Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/SQLAlchemy-1.3.25.dev0-py3.11-linux-x86_64.egg/sqlalchemy/engine/reflection.py: 18%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

276 statements  

1# engine/reflection.py 

2# Copyright (C) 2005-2021 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 if hasattr(self.dialect, "get_schema_names"): 

156 return self.dialect.get_schema_names( 

157 self.bind, info_cache=self.info_cache 

158 ) 

159 return [] 

160 

161 @util.deprecated_params( 

162 order_by=( 

163 "1.0", 

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

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

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

167 "for a " 

168 "more comprehensive solution to resolving foreign key cycles " 

169 "between tables.", 

170 ) 

171 ) 

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

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

174 

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

176 Views are instead returned using the 

177 :meth:`_reflection.Inspector.get_view_names` 

178 method. 

179 

180 

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

182 database's default schema is 

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

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

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

186 

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

188 the result on foreign key dependencies. Does not automatically 

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

190 if cycles exist. 

191 

192 .. seealso:: 

193 

194 :meth:`_reflection.Inspector.get_sorted_table_and_fkc_names` 

195 

196 :attr:`_schema.MetaData.sorted_tables` 

197 

198 """ 

199 

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

201 tnames = self.dialect.get_table_names( 

202 self.bind, schema, info_cache=self.info_cache 

203 ) 

204 else: 

205 tnames = self.engine.table_names(schema) 

206 if order_by == "foreign_key": 

207 tuples = [] 

208 for tname in tnames: 

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

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

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

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

213 return tnames 

214 

215 def get_sorted_table_and_fkc_names(self, schema=None): 

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

217 referred to within a particular schema. 

218 

219 This will yield 2-tuples of 

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

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

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

223 The final element 

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

225 which will consist of remaining 

226 foreign key constraint names that would require a separate CREATE 

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

228 

229 .. versionadded:: 1.0.- 

230 

231 .. seealso:: 

232 

233 :meth:`_reflection.Inspector.get_table_names` 

234 

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

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

237 

238 """ 

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

240 tnames = self.dialect.get_table_names( 

241 self.bind, schema, info_cache=self.info_cache 

242 ) 

243 else: 

244 tnames = self.engine.table_names(schema) 

245 

246 tuples = set() 

247 remaining_fkcs = set() 

248 

249 fknames_for_table = {} 

250 for tname in tnames: 

251 fkeys = self.get_foreign_keys(tname, schema) 

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

253 for fkey in fkeys: 

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

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

256 try: 

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

258 except exc.CircularDependencyError as err: 

259 for edge in err.edges: 

260 tuples.remove(edge) 

261 remaining_fkcs.update( 

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

263 ) 

264 

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

266 return [ 

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

268 for tname in candidate_sort 

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

270 

271 def get_temp_table_names(self): 

272 """Return a list of temporary table names for the current bind. 

273 

274 This method is unsupported by most dialects; currently 

275 only SQLite implements it. 

276 

277 .. versionadded:: 1.0.0 

278 

279 """ 

280 return self.dialect.get_temp_table_names( 

281 self.bind, info_cache=self.info_cache 

282 ) 

283 

284 def get_temp_view_names(self): 

285 """Return a list of temporary view names for the current bind. 

286 

287 This method is unsupported by most dialects; currently 

288 only SQLite implements it. 

289 

290 .. versionadded:: 1.0.0 

291 

292 """ 

293 return self.dialect.get_temp_view_names( 

294 self.bind, info_cache=self.info_cache 

295 ) 

296 

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

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

299 given name was created. 

300 

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

302 

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

304 use :class:`.quoted_name`. 

305 

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

307 of the database connection. For special quoting, 

308 use :class:`.quoted_name`. 

309 

310 """ 

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

312 return self.dialect.get_table_options( 

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

314 ) 

315 return {} 

316 

317 def get_view_names(self, schema=None): 

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

319 

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

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

322 

323 """ 

324 

325 return self.dialect.get_view_names( 

326 self.bind, schema, info_cache=self.info_cache 

327 ) 

328 

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

330 """Return definition for `view_name`. 

331 

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

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

334 

335 """ 

336 

337 return self.dialect.get_view_definition( 

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

339 ) 

340 

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

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

343 

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

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

346 

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

348 

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

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

351 

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

353 

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

355 as a string SQL expression. 

356 

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

358 this is returned as a boolean or 'auto' 

359 

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

361 dialects return this key 

362 

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

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

365 Returned as a dict with the keys: 

366 

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

368 as a string SQL expression 

369 

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

371 stored in the table 

372 

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

374 

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

376 

377 

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

379 use :class:`.quoted_name`. 

380 

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

382 of the database connection. For special quoting, 

383 use :class:`.quoted_name`. 

384 

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

386 a database column. 

387 

388 """ 

389 

390 col_defs = self.dialect.get_columns( 

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

392 ) 

393 for col_def in col_defs: 

394 # make this easy and only return instances for coltype 

395 coltype = col_def["type"] 

396 if not isinstance(coltype, TypeEngine): 

397 col_def["type"] = coltype() 

398 return col_defs 

399 

400 @deprecated( 

401 "0.7", 

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

403 "method is deprecated and " 

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

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

406 ) 

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

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

409 

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

411 primary key information as a list of column names. 

412 """ 

413 

414 return self.dialect.get_pk_constraint( 

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

416 )["constrained_columns"] 

417 

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

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

420 

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

422 primary key information as a dictionary with these keys: 

423 

424 * ``constrained_columns`` - 

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

426 

427 * ``name`` - 

428 optional name of the primary key constraint. 

429 

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

431 use :class:`.quoted_name`. 

432 

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

434 of the database connection. For special quoting, 

435 use :class:`.quoted_name`. 

436 

437 """ 

438 return self.dialect.get_pk_constraint( 

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

440 ) 

441 

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

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

444 

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

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

447 

448 * ``constrained_columns`` - 

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

450 

451 * ``referred_schema`` - 

452 the name of the referred schema 

453 

454 * ``referred_table`` - 

455 the name of the referred table 

456 

457 * ``referred_columns`` - 

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

459 constrained_columns 

460 

461 * ``name`` - 

462 optional name of the foreign key constraint. 

463 

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

465 use :class:`.quoted_name`. 

466 

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

468 of the database connection. For special quoting, 

469 use :class:`.quoted_name`. 

470 

471 """ 

472 

473 return self.dialect.get_foreign_keys( 

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

475 ) 

476 

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

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

479 

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

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

482 

483 * ``name`` - 

484 the index's name 

485 

486 * ``column_names`` - 

487 list of column names in order 

488 

489 * ``unique`` - 

490 boolean 

491 

492 * ``column_sorting`` - 

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

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

495 

496 .. versionadded:: 1.3.5 

497 

498 * ``dialect_options`` - 

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

500 for all dialects. 

501 

502 .. versionadded:: 1.0.0 

503 

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

505 use :class:`.quoted_name`. 

506 

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

508 of the database connection. For special quoting, 

509 use :class:`.quoted_name`. 

510 

511 """ 

512 

513 return self.dialect.get_indexes( 

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

515 ) 

516 

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

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

519 

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

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

522 

523 * ``name`` - 

524 the unique constraint's name 

525 

526 * ``column_names`` - 

527 list of column names in order 

528 

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

530 use :class:`.quoted_name`. 

531 

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

533 of the database connection. For special quoting, 

534 use :class:`.quoted_name`. 

535 

536 """ 

537 

538 return self.dialect.get_unique_constraints( 

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

540 ) 

541 

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

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

544 

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

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

547 

548 * ``text`` - 

549 text of the comment. 

550 

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

552 comments. 

553 

554 .. versionadded:: 1.2 

555 

556 """ 

557 

558 return self.dialect.get_table_comment( 

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

560 ) 

561 

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

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

564 

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

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

567 

568 * ``name`` - 

569 the check constraint's name 

570 

571 * ``sqltext`` - 

572 the check constraint's SQL expression 

573 

574 * ``dialect_options`` - 

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

576 dialect-specific options for this CHECK constraint 

577 

578 .. versionadded:: 1.3.8 

579 

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

581 use :class:`.quoted_name`. 

582 

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

584 of the database connection. For special quoting, 

585 use :class:`.quoted_name`. 

586 

587 .. versionadded:: 1.1.0 

588 

589 """ 

590 

591 return self.dialect.get_check_constraints( 

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

593 ) 

594 

595 def reflecttable( 

596 self, 

597 table, 

598 include_columns, 

599 exclude_columns=(), 

600 resolve_fks=True, 

601 _extend_on=None, 

602 ): 

603 """Given a :class:`_schema.Table` object, load its internal 

604 constructs based on introspection. 

605 

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

607 table reflection. Direct usage is like:: 

608 

609 from sqlalchemy import create_engine, MetaData, Table 

610 from sqlalchemy.engine.reflection import Inspector 

611 

612 engine = create_engine('...') 

613 meta = MetaData() 

614 user_table = Table('user', meta) 

615 insp = Inspector.from_engine(engine) 

616 insp.reflecttable(user_table, None) 

617 

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

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

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

621 

622 """ 

623 

624 if _extend_on is not None: 

625 if table in _extend_on: 

626 return 

627 else: 

628 _extend_on.add(table) 

629 

630 dialect = self.bind.dialect 

631 

632 schema = self.bind.schema_for_object(table) 

633 

634 table_name = table.name 

635 

636 # get table-level arguments that are specifically 

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

638 # these are unconditionally passed to related Table 

639 # objects 

640 reflection_options = dict( 

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

642 for k in dialect.reflection_options 

643 if k in table.dialect_kwargs 

644 ) 

645 

646 # reflect table options, like mysql_engine 

647 tbl_opts = self.get_table_options( 

648 table_name, schema, **table.dialect_kwargs 

649 ) 

650 if tbl_opts: 

651 # add additional kwargs to the Table if the dialect 

652 # returned them 

653 table._validate_dialect_kwargs(tbl_opts) 

654 

655 if util.py2k: 

656 if isinstance(schema, str): 

657 schema = schema.decode(dialect.encoding) 

658 if isinstance(table_name, str): 

659 table_name = table_name.decode(dialect.encoding) 

660 

661 found_table = False 

662 cols_by_orig_name = {} 

663 

664 for col_d in self.get_columns( 

665 table_name, schema, **table.dialect_kwargs 

666 ): 

667 found_table = True 

668 

669 self._reflect_column( 

670 table, 

671 col_d, 

672 include_columns, 

673 exclude_columns, 

674 cols_by_orig_name, 

675 ) 

676 

677 if not found_table: 

678 raise exc.NoSuchTableError(table.name) 

679 

680 self._reflect_pk( 

681 table_name, schema, table, cols_by_orig_name, exclude_columns 

682 ) 

683 

684 self._reflect_fk( 

685 table_name, 

686 schema, 

687 table, 

688 cols_by_orig_name, 

689 exclude_columns, 

690 resolve_fks, 

691 _extend_on, 

692 reflection_options, 

693 ) 

694 

695 self._reflect_indexes( 

696 table_name, 

697 schema, 

698 table, 

699 cols_by_orig_name, 

700 include_columns, 

701 exclude_columns, 

702 reflection_options, 

703 ) 

704 

705 self._reflect_unique_constraints( 

706 table_name, 

707 schema, 

708 table, 

709 cols_by_orig_name, 

710 include_columns, 

711 exclude_columns, 

712 reflection_options, 

713 ) 

714 

715 self._reflect_check_constraints( 

716 table_name, 

717 schema, 

718 table, 

719 cols_by_orig_name, 

720 include_columns, 

721 exclude_columns, 

722 reflection_options, 

723 ) 

724 

725 self._reflect_table_comment( 

726 table_name, schema, table, reflection_options 

727 ) 

728 

729 def _reflect_column( 

730 self, table, col_d, include_columns, exclude_columns, cols_by_orig_name 

731 ): 

732 

733 orig_name = col_d["name"] 

734 

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

736 

737 # fetch name again as column_reflect is allowed to 

738 # change it 

739 name = col_d["name"] 

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

741 exclude_columns and name in exclude_columns 

742 ): 

743 return 

744 

745 coltype = col_d["type"] 

746 

747 col_kw = dict( 

748 (k, col_d[k]) 

749 for k in [ 

750 "nullable", 

751 "autoincrement", 

752 "quote", 

753 "info", 

754 "key", 

755 "comment", 

756 ] 

757 if k in col_d 

758 ) 

759 

760 if "dialect_options" in col_d: 

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

762 

763 colargs = [] 

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

765 default = col_d["default"] 

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

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

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

769 default = sa_schema.DefaultClause( 

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

771 ) 

772 

773 colargs.append(default) 

774 

775 if "computed" in col_d: 

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

777 colargs.append(computed) 

778 

779 if "sequence" in col_d: 

780 self._reflect_col_sequence(col_d, colargs) 

781 

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

783 name, coltype, *colargs, **col_kw 

784 ) 

785 

786 if col.key in table.primary_key: 

787 col.primary_key = True 

788 table.append_column(col) 

789 

790 def _reflect_col_sequence(self, col_d, colargs): 

791 if "sequence" in col_d: 

792 # TODO: mssql and sybase are using this. 

793 seq = col_d["sequence"] 

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

795 if "start" in seq: 

796 sequence.start = seq["start"] 

797 if "increment" in seq: 

798 sequence.increment = seq["increment"] 

799 colargs.append(sequence) 

800 

801 def _reflect_pk( 

802 self, table_name, schema, table, cols_by_orig_name, exclude_columns 

803 ): 

804 pk_cons = self.get_pk_constraint( 

805 table_name, schema, **table.dialect_kwargs 

806 ) 

807 if pk_cons: 

808 pk_cols = [ 

809 cols_by_orig_name[pk] 

810 for pk in pk_cons["constrained_columns"] 

811 if pk in cols_by_orig_name and pk not in exclude_columns 

812 ] 

813 

814 # update pk constraint name 

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

816 

817 # tell the PKConstraint to re-initialize 

818 # its column collection 

819 table.primary_key._reload(pk_cols) 

820 

821 def _reflect_fk( 

822 self, 

823 table_name, 

824 schema, 

825 table, 

826 cols_by_orig_name, 

827 exclude_columns, 

828 resolve_fks, 

829 _extend_on, 

830 reflection_options, 

831 ): 

832 fkeys = self.get_foreign_keys( 

833 table_name, schema, **table.dialect_kwargs 

834 ) 

835 for fkey_d in fkeys: 

836 conname = fkey_d["name"] 

837 # look for columns by orig name in cols_by_orig_name, 

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

839 constrained_columns = [ 

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

841 for c in fkey_d["constrained_columns"] 

842 ] 

843 if exclude_columns and set(constrained_columns).intersection( 

844 exclude_columns 

845 ): 

846 continue 

847 referred_schema = fkey_d["referred_schema"] 

848 referred_table = fkey_d["referred_table"] 

849 referred_columns = fkey_d["referred_columns"] 

850 refspec = [] 

851 if referred_schema is not None: 

852 if resolve_fks: 

853 sa_schema.Table( 

854 referred_table, 

855 table.metadata, 

856 autoload=True, 

857 schema=referred_schema, 

858 autoload_with=self.bind, 

859 _extend_on=_extend_on, 

860 **reflection_options 

861 ) 

862 for column in referred_columns: 

863 refspec.append( 

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

865 ) 

866 else: 

867 if resolve_fks: 

868 sa_schema.Table( 

869 referred_table, 

870 table.metadata, 

871 autoload=True, 

872 autoload_with=self.bind, 

873 schema=sa_schema.BLANK_SCHEMA, 

874 _extend_on=_extend_on, 

875 **reflection_options 

876 ) 

877 for column in referred_columns: 

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

879 if "options" in fkey_d: 

880 options = fkey_d["options"] 

881 else: 

882 options = {} 

883 table.append_constraint( 

884 sa_schema.ForeignKeyConstraint( 

885 constrained_columns, 

886 refspec, 

887 conname, 

888 link_to_name=True, 

889 **options 

890 ) 

891 ) 

892 

893 _index_sort_exprs = [ 

894 ("asc", operators.asc_op), 

895 ("desc", operators.desc_op), 

896 ("nullsfirst", operators.nullsfirst_op), 

897 ("nullslast", operators.nullslast_op), 

898 ] 

899 

900 def _reflect_indexes( 

901 self, 

902 table_name, 

903 schema, 

904 table, 

905 cols_by_orig_name, 

906 include_columns, 

907 exclude_columns, 

908 reflection_options, 

909 ): 

910 # Indexes 

911 indexes = self.get_indexes(table_name, schema) 

912 for index_d in indexes: 

913 name = index_d["name"] 

914 columns = index_d["column_names"] 

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

916 unique = index_d["unique"] 

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

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

919 

920 duplicates = index_d.get("duplicates_constraint") 

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

922 util.warn( 

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

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

925 ) 

926 continue 

927 if duplicates: 

928 continue 

929 # look for columns by orig name in cols_by_orig_name, 

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

931 idx_cols = [] 

932 for c in columns: 

933 try: 

934 idx_col = ( 

935 cols_by_orig_name[c] 

936 if c in cols_by_orig_name 

937 else table.c[c] 

938 ) 

939 except KeyError: 

940 util.warn( 

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

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

943 ) 

944 continue 

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

946 for k, op in self._index_sort_exprs: 

947 if k in c_sorting: 

948 idx_col = op(idx_col) 

949 idx_cols.append(idx_col) 

950 

951 sa_schema.Index( 

952 name, 

953 *idx_cols, 

954 _table=table, 

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

956 ) 

957 

958 def _reflect_unique_constraints( 

959 self, 

960 table_name, 

961 schema, 

962 table, 

963 cols_by_orig_name, 

964 include_columns, 

965 exclude_columns, 

966 reflection_options, 

967 ): 

968 

969 # Unique Constraints 

970 try: 

971 constraints = self.get_unique_constraints(table_name, schema) 

972 except NotImplementedError: 

973 # optional dialect feature 

974 return 

975 

976 for const_d in constraints: 

977 conname = const_d["name"] 

978 columns = const_d["column_names"] 

979 duplicates = const_d.get("duplicates_index") 

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

981 util.warn( 

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

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

984 ) 

985 continue 

986 if duplicates: 

987 continue 

988 # look for columns by orig name in cols_by_orig_name, 

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

990 constrained_cols = [] 

991 for c in columns: 

992 try: 

993 constrained_col = ( 

994 cols_by_orig_name[c] 

995 if c in cols_by_orig_name 

996 else table.c[c] 

997 ) 

998 except KeyError: 

999 util.warn( 

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

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

1002 ) 

1003 else: 

1004 constrained_cols.append(constrained_col) 

1005 table.append_constraint( 

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

1007 ) 

1008 

1009 def _reflect_check_constraints( 

1010 self, 

1011 table_name, 

1012 schema, 

1013 table, 

1014 cols_by_orig_name, 

1015 include_columns, 

1016 exclude_columns, 

1017 reflection_options, 

1018 ): 

1019 try: 

1020 constraints = self.get_check_constraints(table_name, schema) 

1021 except NotImplementedError: 

1022 # optional dialect feature 

1023 return 

1024 

1025 for const_d in constraints: 

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

1027 

1028 def _reflect_table_comment( 

1029 self, table_name, schema, table, reflection_options 

1030 ): 

1031 try: 

1032 comment_dict = self.get_table_comment(table_name, schema) 

1033 except NotImplementedError: 

1034 return 

1035 else: 

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