Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/sqlalchemy/engine/reflection.py: 21%
331 statements
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-07 06:35 +0000
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-07 06:35 +0000
1# engine/reflection.py
2# Copyright (C) 2005-2023 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: https://www.opensource.org/licenses/mit-license.php
8"""Provides an abstraction for obtaining database schema information.
10Usage Notes:
12Here are some general conventions when accessing the low level inspector
13methods such as get_table_names, get_columns, etc.
151. Inspector methods return lists of dicts in most cases for the following
16 reasons:
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]).
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"""
28import contextlib
30from .base import Connectable
31from .base import Connection
32from .base import Engine
33from .. import exc
34from .. import inspection
35from .. import sql
36from .. import util
37from ..sql import operators
38from ..sql import schema as sa_schema
39from ..sql.type_api import TypeEngine
40from ..util import topological
43@util.decorator
44def cache(fn, self, con, *args, **kw):
45 info_cache = kw.get("info_cache", None)
46 if info_cache is None:
47 return fn(self, con, *args, **kw)
48 key = (
49 fn.__name__,
50 tuple(a for a in args if isinstance(a, util.string_types)),
51 tuple((k, v) for k, v in kw.items() if k != "info_cache"),
52 )
53 ret = info_cache.get(key)
54 if ret is None:
55 ret = fn(self, con, *args, **kw)
56 info_cache[key] = ret
57 return ret
60@inspection._self_inspects
61class Inspector(object):
62 """Performs database schema inspection.
64 The Inspector acts as a proxy to the reflection methods of the
65 :class:`~sqlalchemy.engine.interfaces.Dialect`, providing a
66 consistent interface as well as caching support for previously
67 fetched metadata.
69 A :class:`_reflection.Inspector` object is usually created via the
70 :func:`_sa.inspect` function, which may be passed an
71 :class:`_engine.Engine`
72 or a :class:`_engine.Connection`::
74 from sqlalchemy import inspect, create_engine
75 engine = create_engine('...')
76 insp = inspect(engine)
78 Where above, the :class:`~sqlalchemy.engine.interfaces.Dialect` associated
79 with the engine may opt to return an :class:`_reflection.Inspector`
80 subclass that
81 provides additional methods specific to the dialect's target database.
83 """
85 @util.deprecated(
86 "1.4",
87 "The __init__() method on :class:`_reflection.Inspector` "
88 "is deprecated and "
89 "will be removed in a future release. Please use the "
90 ":func:`.sqlalchemy.inspect` "
91 "function on an :class:`_engine.Engine` or "
92 ":class:`_engine.Connection` "
93 "in order to "
94 "acquire an :class:`_reflection.Inspector`.",
95 )
96 def __init__(self, bind):
97 """Initialize a new :class:`_reflection.Inspector`.
99 :param bind: a :class:`~sqlalchemy.engine.Connectable`,
100 which is typically an instance of
101 :class:`~sqlalchemy.engine.Engine` or
102 :class:`~sqlalchemy.engine.Connection`.
104 For a dialect-specific instance of :class:`_reflection.Inspector`, see
105 :meth:`_reflection.Inspector.from_engine`
107 """
108 return self._init_legacy(bind)
110 @classmethod
111 def _construct(cls, init, bind):
113 if hasattr(bind.dialect, "inspector"):
114 cls = bind.dialect.inspector
116 self = cls.__new__(cls)
117 init(self, bind)
118 return self
120 def _init_legacy(self, bind):
121 if hasattr(bind, "exec_driver_sql"):
122 self._init_connection(bind)
123 else:
124 self._init_engine(bind)
126 def _init_engine(self, engine):
127 self.bind = self.engine = engine
128 engine.connect().close()
129 self._op_context_requires_connect = True
130 self.dialect = self.engine.dialect
131 self.info_cache = {}
133 def _init_connection(self, connection):
134 self.bind = connection
135 self.engine = connection.engine
136 self._op_context_requires_connect = False
137 self.dialect = self.engine.dialect
138 self.info_cache = {}
140 @classmethod
141 @util.deprecated(
142 "1.4",
143 "The from_engine() method on :class:`_reflection.Inspector` "
144 "is deprecated and "
145 "will be removed in a future release. Please use the "
146 ":func:`.sqlalchemy.inspect` "
147 "function on an :class:`_engine.Engine` or "
148 ":class:`_engine.Connection` "
149 "in order to "
150 "acquire an :class:`_reflection.Inspector`.",
151 )
152 def from_engine(cls, bind):
153 """Construct a new dialect-specific Inspector object from the given
154 engine or connection.
156 :param bind: a :class:`~sqlalchemy.engine.Connectable`,
157 which is typically an instance of
158 :class:`~sqlalchemy.engine.Engine` or
159 :class:`~sqlalchemy.engine.Connection`.
161 This method differs from direct a direct constructor call of
162 :class:`_reflection.Inspector` in that the
163 :class:`~sqlalchemy.engine.interfaces.Dialect` is given a chance to
164 provide a dialect-specific :class:`_reflection.Inspector` instance,
165 which may
166 provide additional methods.
168 See the example at :class:`_reflection.Inspector`.
170 """
171 return cls._construct(cls._init_legacy, bind)
173 @inspection._inspects(Connectable)
174 def _connectable_insp(bind):
175 # this method should not be used unless some unusual case
176 # has subclassed "Connectable"
178 return Inspector._construct(Inspector._init_legacy, bind)
180 @inspection._inspects(Engine)
181 def _engine_insp(bind):
182 return Inspector._construct(Inspector._init_engine, bind)
184 @inspection._inspects(Connection)
185 def _connection_insp(bind):
186 return Inspector._construct(Inspector._init_connection, bind)
188 @contextlib.contextmanager
189 def _operation_context(self):
190 """Return a context that optimizes for multiple operations on a single
191 transaction.
193 This essentially allows connect()/close() to be called if we detected
194 that we're against an :class:`_engine.Engine` and not a
195 :class:`_engine.Connection`.
197 """
198 if self._op_context_requires_connect:
199 conn = self.bind.connect()
200 else:
201 conn = self.bind
202 try:
203 yield conn
204 finally:
205 if self._op_context_requires_connect:
206 conn.close()
208 @contextlib.contextmanager
209 def _inspection_context(self):
210 """Return an :class:`_reflection.Inspector`
211 from this one that will run all
212 operations on a single connection.
214 """
216 with self._operation_context() as conn:
217 sub_insp = self._construct(self.__class__._init_connection, conn)
218 sub_insp.info_cache = self.info_cache
219 yield sub_insp
221 @property
222 def default_schema_name(self):
223 """Return the default schema name presented by the dialect
224 for the current engine's database user.
226 E.g. this is typically ``public`` for PostgreSQL and ``dbo``
227 for SQL Server.
229 """
230 return self.dialect.default_schema_name
232 def get_schema_names(self):
233 """Return all schema names."""
235 if hasattr(self.dialect, "get_schema_names"):
236 with self._operation_context() as conn:
237 return self.dialect.get_schema_names(
238 conn, info_cache=self.info_cache
239 )
240 return []
242 def get_table_names(self, schema=None):
243 """Return all table names in referred to within a particular schema.
245 The names are expected to be real tables only, not views.
246 Views are instead returned using the
247 :meth:`_reflection.Inspector.get_view_names`
248 method.
251 :param schema: Schema name. If ``schema`` is left at ``None``, the
252 database's default schema is
253 used, else the named schema is searched. If the database does not
254 support named schemas, behavior is undefined if ``schema`` is not
255 passed as ``None``. For special quoting, use :class:`.quoted_name`.
257 .. seealso::
259 :meth:`_reflection.Inspector.get_sorted_table_and_fkc_names`
261 :attr:`_schema.MetaData.sorted_tables`
263 """
265 with self._operation_context() as conn:
266 return self.dialect.get_table_names(
267 conn, schema, info_cache=self.info_cache
268 )
270 def has_table(self, table_name, schema=None):
271 """Return True if the backend has a table of the given name.
274 :param table_name: name of the table to check
275 :param schema: schema name to query, if not the default schema.
277 .. versionadded:: 1.4 - the :meth:`.Inspector.has_table` method
278 replaces the :meth:`_engine.Engine.has_table` method.
280 """
281 # TODO: info_cache?
282 with self._operation_context() as conn:
283 return self.dialect.has_table(conn, table_name, schema)
285 def has_sequence(self, sequence_name, schema=None):
286 """Return True if the backend has a table of the given name.
288 :param sequence_name: name of the table to check
289 :param schema: schema name to query, if not the default schema.
291 .. versionadded:: 1.4
293 """
294 # TODO: info_cache?
295 with self._operation_context() as conn:
296 return self.dialect.has_sequence(conn, sequence_name, schema)
298 def get_sorted_table_and_fkc_names(self, schema=None):
299 """Return dependency-sorted table and foreign key constraint names in
300 referred to within a particular schema.
302 This will yield 2-tuples of
303 ``(tablename, [(tname, fkname), (tname, fkname), ...])``
304 consisting of table names in CREATE order grouped with the foreign key
305 constraint names that are not detected as belonging to a cycle.
306 The final element
307 will be ``(None, [(tname, fkname), (tname, fkname), ..])``
308 which will consist of remaining
309 foreign key constraint names that would require a separate CREATE
310 step after-the-fact, based on dependencies between tables.
312 .. versionadded:: 1.0.-
314 .. seealso::
316 :meth:`_reflection.Inspector.get_table_names`
318 :func:`.sort_tables_and_constraints` - similar method which works
319 with an already-given :class:`_schema.MetaData`.
321 """
323 with self._operation_context() as conn:
324 tnames = self.dialect.get_table_names(
325 conn, schema, info_cache=self.info_cache
326 )
328 tuples = set()
329 remaining_fkcs = set()
331 fknames_for_table = {}
332 for tname in tnames:
333 fkeys = self.get_foreign_keys(tname, schema)
334 fknames_for_table[tname] = set([fk["name"] for fk in fkeys])
335 for fkey in fkeys:
336 if tname != fkey["referred_table"]:
337 tuples.add((fkey["referred_table"], tname))
338 try:
339 candidate_sort = list(topological.sort(tuples, tnames))
340 except exc.CircularDependencyError as err:
341 for edge in err.edges:
342 tuples.remove(edge)
343 remaining_fkcs.update(
344 (edge[1], fkc) for fkc in fknames_for_table[edge[1]]
345 )
347 candidate_sort = list(topological.sort(tuples, tnames))
348 return [
349 (tname, fknames_for_table[tname].difference(remaining_fkcs))
350 for tname in candidate_sort
351 ] + [(None, list(remaining_fkcs))]
353 def get_temp_table_names(self):
354 """Return a list of temporary table names for the current bind.
356 This method is unsupported by most dialects; currently
357 only SQLite implements it.
359 .. versionadded:: 1.0.0
361 """
363 with self._operation_context() as conn:
364 return self.dialect.get_temp_table_names(
365 conn, info_cache=self.info_cache
366 )
368 def get_temp_view_names(self):
369 """Return a list of temporary view names for the current bind.
371 This method is unsupported by most dialects; currently
372 only SQLite implements it.
374 .. versionadded:: 1.0.0
376 """
377 with self._operation_context() as conn:
378 return self.dialect.get_temp_view_names(
379 conn, info_cache=self.info_cache
380 )
382 def get_table_options(self, table_name, schema=None, **kw):
383 """Return a dictionary of options specified when the table of the
384 given name was created.
386 This currently includes some options that apply to MySQL tables.
388 :param table_name: string name of the table. For special quoting,
389 use :class:`.quoted_name`.
391 :param schema: string schema name; if omitted, uses the default schema
392 of the database connection. For special quoting,
393 use :class:`.quoted_name`.
395 """
396 if hasattr(self.dialect, "get_table_options"):
397 with self._operation_context() as conn:
398 return self.dialect.get_table_options(
399 conn, table_name, schema, info_cache=self.info_cache, **kw
400 )
401 return {}
403 def get_view_names(self, schema=None):
404 """Return all view names in `schema`.
406 :param schema: Optional, retrieve names from a non-default schema.
407 For special quoting, use :class:`.quoted_name`.
409 """
411 with self._operation_context() as conn:
412 return self.dialect.get_view_names(
413 conn, schema, info_cache=self.info_cache
414 )
416 def get_sequence_names(self, schema=None):
417 """Return all sequence names in `schema`.
419 :param schema: Optional, retrieve names from a non-default schema.
420 For special quoting, use :class:`.quoted_name`.
422 """
424 with self._operation_context() as conn:
425 return self.dialect.get_sequence_names(
426 conn, schema, info_cache=self.info_cache
427 )
429 def get_view_definition(self, view_name, schema=None):
430 """Return definition for `view_name`.
432 :param schema: Optional, retrieve names from a non-default schema.
433 For special quoting, use :class:`.quoted_name`.
435 """
437 with self._operation_context() as conn:
438 return self.dialect.get_view_definition(
439 conn, view_name, schema, info_cache=self.info_cache
440 )
442 def get_columns(self, table_name, schema=None, **kw):
443 """Return information about columns in `table_name`.
445 Given a string `table_name` and an optional string `schema`, return
446 column information as a list of dicts with these keys:
448 * ``name`` - the column's name
450 * ``type`` - the type of this column; an instance of
451 :class:`~sqlalchemy.types.TypeEngine`
453 * ``nullable`` - boolean flag if the column is NULL or NOT NULL
455 * ``default`` - the column's server default value - this is returned
456 as a string SQL expression.
458 * ``autoincrement`` - indicates that the column is auto incremented -
459 this is returned as a boolean or 'auto'
461 * ``comment`` - (optional) the comment on the column. Only some
462 dialects return this key
464 * ``computed`` - (optional) when present it indicates that this column
465 is computed by the database. Only some dialects return this key.
466 Returned as a dict with the keys:
468 * ``sqltext`` - the expression used to generate this column returned
469 as a string SQL expression
471 * ``persisted`` - (optional) boolean that indicates if the column is
472 stored in the table
474 .. versionadded:: 1.3.16 - added support for computed reflection.
476 * ``identity`` - (optional) when present it indicates that this column
477 is a generated always column. Only some dialects return this key.
478 For a list of keywords on this dict see :class:`_schema.Identity`.
480 .. versionadded:: 1.4 - added support for identity column reflection.
482 * ``dialect_options`` - (optional) a dict with dialect specific options
484 :param table_name: string name of the table. For special quoting,
485 use :class:`.quoted_name`.
487 :param schema: string schema name; if omitted, uses the default schema
488 of the database connection. For special quoting,
489 use :class:`.quoted_name`.
491 :return: list of dictionaries, each representing the definition of
492 a database column.
494 """
496 with self._operation_context() as conn:
497 col_defs = self.dialect.get_columns(
498 conn, table_name, schema, info_cache=self.info_cache, **kw
499 )
500 for col_def in col_defs:
501 # make this easy and only return instances for coltype
502 coltype = col_def["type"]
503 if not isinstance(coltype, TypeEngine):
504 col_def["type"] = coltype()
505 return col_defs
507 def get_pk_constraint(self, table_name, schema=None, **kw):
508 """Return information about primary key constraint on `table_name`.
510 Given a string `table_name`, and an optional string `schema`, return
511 primary key information as a dictionary with these keys:
513 * ``constrained_columns`` -
514 a list of column names that make up the primary key
516 * ``name`` -
517 optional name of the primary key constraint.
519 :param table_name: string name of the table. For special quoting,
520 use :class:`.quoted_name`.
522 :param schema: string schema name; if omitted, uses the default schema
523 of the database connection. For special quoting,
524 use :class:`.quoted_name`.
526 """
527 with self._operation_context() as conn:
528 return self.dialect.get_pk_constraint(
529 conn, table_name, schema, info_cache=self.info_cache, **kw
530 )
532 def get_foreign_keys(self, table_name, schema=None, **kw):
533 """Return information about foreign_keys in `table_name`.
535 Given a string `table_name`, and an optional string `schema`, return
536 foreign key information as a list of dicts with these keys:
538 * ``constrained_columns`` -
539 a list of column names that make up the foreign key
541 * ``referred_schema`` -
542 the name of the referred schema
544 * ``referred_table`` -
545 the name of the referred table
547 * ``referred_columns`` -
548 a list of column names in the referred table that correspond to
549 constrained_columns
551 * ``name`` -
552 optional name of the foreign key constraint.
554 :param table_name: string name of the table. For special quoting,
555 use :class:`.quoted_name`.
557 :param schema: string schema name; if omitted, uses the default schema
558 of the database connection. For special quoting,
559 use :class:`.quoted_name`.
561 """
563 with self._operation_context() as conn:
564 return self.dialect.get_foreign_keys(
565 conn, table_name, schema, info_cache=self.info_cache, **kw
566 )
568 def get_indexes(self, table_name, schema=None, **kw):
569 """Return information about indexes in `table_name`.
571 Given a string `table_name` and an optional string `schema`, return
572 index information as a list of dicts with these keys:
574 * ``name`` -
575 the index's name
577 * ``column_names`` -
578 list of column names in order
580 * ``unique`` -
581 boolean
583 * ``column_sorting`` -
584 optional dict mapping column names to tuple of sort keywords,
585 which may include ``asc``, ``desc``, ``nulls_first``, ``nulls_last``.
587 .. versionadded:: 1.3.5
589 * ``dialect_options`` -
590 dict of dialect-specific index options. May not be present
591 for all dialects.
593 .. versionadded:: 1.0.0
595 :param table_name: string name of the table. For special quoting,
596 use :class:`.quoted_name`.
598 :param schema: string schema name; if omitted, uses the default schema
599 of the database connection. For special quoting,
600 use :class:`.quoted_name`.
602 """
604 with self._operation_context() as conn:
605 return self.dialect.get_indexes(
606 conn, table_name, schema, info_cache=self.info_cache, **kw
607 )
609 def get_unique_constraints(self, table_name, schema=None, **kw):
610 """Return information about unique constraints in `table_name`.
612 Given a string `table_name` and an optional string `schema`, return
613 unique constraint information as a list of dicts with these keys:
615 * ``name`` -
616 the unique constraint's name
618 * ``column_names`` -
619 list of column names in order
621 :param table_name: string name of the table. For special quoting,
622 use :class:`.quoted_name`.
624 :param schema: string schema name; if omitted, uses the default schema
625 of the database connection. For special quoting,
626 use :class:`.quoted_name`.
628 """
630 with self._operation_context() as conn:
631 return self.dialect.get_unique_constraints(
632 conn, table_name, schema, info_cache=self.info_cache, **kw
633 )
635 def get_table_comment(self, table_name, schema=None, **kw):
636 """Return information about the table comment for ``table_name``.
638 Given a string ``table_name`` and an optional string ``schema``,
639 return table comment information as a dictionary with these keys:
641 * ``text`` -
642 text of the comment.
644 Raises ``NotImplementedError`` for a dialect that does not support
645 comments.
647 .. versionadded:: 1.2
649 """
651 with self._operation_context() as conn:
652 return self.dialect.get_table_comment(
653 conn, table_name, schema, info_cache=self.info_cache, **kw
654 )
656 def get_check_constraints(self, table_name, schema=None, **kw):
657 """Return information about check constraints in `table_name`.
659 Given a string `table_name` and an optional string `schema`, return
660 check constraint information as a list of dicts with these keys:
662 * ``name`` -
663 the check constraint's name
665 * ``sqltext`` -
666 the check constraint's SQL expression
668 * ``dialect_options`` -
669 may or may not be present; a dictionary with additional
670 dialect-specific options for this CHECK constraint
672 .. versionadded:: 1.3.8
674 :param table_name: string name of the table. For special quoting,
675 use :class:`.quoted_name`.
677 :param schema: string schema name; if omitted, uses the default schema
678 of the database connection. For special quoting,
679 use :class:`.quoted_name`.
681 .. versionadded:: 1.1.0
683 """
685 with self._operation_context() as conn:
686 return self.dialect.get_check_constraints(
687 conn, table_name, schema, info_cache=self.info_cache, **kw
688 )
690 @util.deprecated_20(
691 ":meth:`_reflection.Inspector.reflecttable`",
692 "The :meth:`_reflection.Inspector.reflecttable` "
693 "method was renamed to "
694 ":meth:`_reflection.Inspector.reflect_table`. This deprecated alias "
695 "will be removed in a future release.",
696 )
697 def reflecttable(self, *args, **kwargs):
698 "See reflect_table. This method name is deprecated"
699 return self.reflect_table(*args, **kwargs)
701 def reflect_table(
702 self,
703 table,
704 include_columns,
705 exclude_columns=(),
706 resolve_fks=True,
707 _extend_on=None,
708 ):
709 """Given a :class:`_schema.Table` object, load its internal
710 constructs based on introspection.
712 This is the underlying method used by most dialects to produce
713 table reflection. Direct usage is like::
715 from sqlalchemy import create_engine, MetaData, Table
716 from sqlalchemy import inspect
718 engine = create_engine('...')
719 meta = MetaData()
720 user_table = Table('user', meta)
721 insp = inspect(engine)
722 insp.reflect_table(user_table, None)
724 .. versionchanged:: 1.4 Renamed from ``reflecttable`` to
725 ``reflect_table``
727 :param table: a :class:`~sqlalchemy.schema.Table` instance.
728 :param include_columns: a list of string column names to include
729 in the reflection process. If ``None``, all columns are reflected.
731 """
733 if _extend_on is not None:
734 if table in _extend_on:
735 return
736 else:
737 _extend_on.add(table)
739 dialect = self.bind.dialect
741 with self._operation_context() as conn:
742 schema = conn.schema_for_object(table)
744 table_name = table.name
746 # get table-level arguments that are specifically
747 # intended for reflection, e.g. oracle_resolve_synonyms.
748 # these are unconditionally passed to related Table
749 # objects
750 reflection_options = dict(
751 (k, table.dialect_kwargs.get(k))
752 for k in dialect.reflection_options
753 if k in table.dialect_kwargs
754 )
756 # reflect table options, like mysql_engine
757 tbl_opts = self.get_table_options(
758 table_name, schema, **table.dialect_kwargs
759 )
760 if tbl_opts:
761 # add additional kwargs to the Table if the dialect
762 # returned them
763 table._validate_dialect_kwargs(tbl_opts)
765 if util.py2k:
766 if isinstance(schema, str):
767 schema = schema.decode(dialect.encoding)
768 if isinstance(table_name, str):
769 table_name = table_name.decode(dialect.encoding)
771 found_table = False
772 cols_by_orig_name = {}
774 for col_d in self.get_columns(
775 table_name, schema, **table.dialect_kwargs
776 ):
777 found_table = True
779 self._reflect_column(
780 table,
781 col_d,
782 include_columns,
783 exclude_columns,
784 cols_by_orig_name,
785 )
787 # NOTE: support tables/views with no columns
788 if not found_table and not self.has_table(table_name, schema):
789 raise exc.NoSuchTableError(table_name)
791 self._reflect_pk(
792 table_name, schema, table, cols_by_orig_name, exclude_columns
793 )
795 self._reflect_fk(
796 table_name,
797 schema,
798 table,
799 cols_by_orig_name,
800 include_columns,
801 exclude_columns,
802 resolve_fks,
803 _extend_on,
804 reflection_options,
805 )
807 self._reflect_indexes(
808 table_name,
809 schema,
810 table,
811 cols_by_orig_name,
812 include_columns,
813 exclude_columns,
814 reflection_options,
815 )
817 self._reflect_unique_constraints(
818 table_name,
819 schema,
820 table,
821 cols_by_orig_name,
822 include_columns,
823 exclude_columns,
824 reflection_options,
825 )
827 self._reflect_check_constraints(
828 table_name,
829 schema,
830 table,
831 cols_by_orig_name,
832 include_columns,
833 exclude_columns,
834 reflection_options,
835 )
837 self._reflect_table_comment(
838 table_name, schema, table, reflection_options
839 )
841 def _reflect_column(
842 self, table, col_d, include_columns, exclude_columns, cols_by_orig_name
843 ):
845 orig_name = col_d["name"]
847 table.metadata.dispatch.column_reflect(self, table, col_d)
848 table.dispatch.column_reflect(self, table, col_d)
850 # fetch name again as column_reflect is allowed to
851 # change it
852 name = col_d["name"]
853 if (include_columns and name not in include_columns) or (
854 exclude_columns and name in exclude_columns
855 ):
856 return
858 coltype = col_d["type"]
860 col_kw = dict(
861 (k, col_d[k])
862 for k in [
863 "nullable",
864 "autoincrement",
865 "quote",
866 "info",
867 "key",
868 "comment",
869 ]
870 if k in col_d
871 )
873 if "dialect_options" in col_d:
874 col_kw.update(col_d["dialect_options"])
876 colargs = []
877 if col_d.get("default") is not None:
878 default = col_d["default"]
879 if isinstance(default, sql.elements.TextClause):
880 default = sa_schema.DefaultClause(default, _reflected=True)
881 elif not isinstance(default, sa_schema.FetchedValue):
882 default = sa_schema.DefaultClause(
883 sql.text(col_d["default"]), _reflected=True
884 )
886 colargs.append(default)
888 if "computed" in col_d:
889 computed = sa_schema.Computed(**col_d["computed"])
890 colargs.append(computed)
892 if "identity" in col_d:
893 computed = sa_schema.Identity(**col_d["identity"])
894 colargs.append(computed)
896 if "sequence" in col_d:
897 self._reflect_col_sequence(col_d, colargs)
899 cols_by_orig_name[orig_name] = col = sa_schema.Column(
900 name, coltype, *colargs, **col_kw
901 )
903 if col.key in table.primary_key:
904 col.primary_key = True
905 table.append_column(col, replace_existing=True)
907 def _reflect_col_sequence(self, col_d, colargs):
908 if "sequence" in col_d:
909 # TODO: mssql and sybase are using this.
910 seq = col_d["sequence"]
911 sequence = sa_schema.Sequence(seq["name"], 1, 1)
912 if "start" in seq:
913 sequence.start = seq["start"]
914 if "increment" in seq:
915 sequence.increment = seq["increment"]
916 colargs.append(sequence)
918 def _reflect_pk(
919 self, table_name, schema, table, cols_by_orig_name, exclude_columns
920 ):
921 pk_cons = self.get_pk_constraint(
922 table_name, schema, **table.dialect_kwargs
923 )
924 if pk_cons:
925 pk_cols = [
926 cols_by_orig_name[pk]
927 for pk in pk_cons["constrained_columns"]
928 if pk in cols_by_orig_name and pk not in exclude_columns
929 ]
931 # update pk constraint name
932 table.primary_key.name = pk_cons.get("name")
934 # tell the PKConstraint to re-initialize
935 # its column collection
936 table.primary_key._reload(pk_cols)
938 def _reflect_fk(
939 self,
940 table_name,
941 schema,
942 table,
943 cols_by_orig_name,
944 include_columns,
945 exclude_columns,
946 resolve_fks,
947 _extend_on,
948 reflection_options,
949 ):
950 fkeys = self.get_foreign_keys(
951 table_name, schema, **table.dialect_kwargs
952 )
953 for fkey_d in fkeys:
954 conname = fkey_d["name"]
955 # look for columns by orig name in cols_by_orig_name,
956 # but support columns that are in-Python only as fallback
957 constrained_columns = [
958 cols_by_orig_name[c].key if c in cols_by_orig_name else c
959 for c in fkey_d["constrained_columns"]
960 ]
962 if (
963 exclude_columns
964 and set(constrained_columns).intersection(exclude_columns)
965 or (
966 include_columns
967 and set(constrained_columns).difference(include_columns)
968 )
969 ):
970 continue
972 referred_schema = fkey_d["referred_schema"]
973 referred_table = fkey_d["referred_table"]
974 referred_columns = fkey_d["referred_columns"]
975 refspec = []
976 if referred_schema is not None:
977 if resolve_fks:
978 sa_schema.Table(
979 referred_table,
980 table.metadata,
981 schema=referred_schema,
982 autoload_with=self.bind,
983 _extend_on=_extend_on,
984 **reflection_options
985 )
986 for column in referred_columns:
987 refspec.append(
988 ".".join([referred_schema, referred_table, column])
989 )
990 else:
991 if resolve_fks:
992 sa_schema.Table(
993 referred_table,
994 table.metadata,
995 autoload_with=self.bind,
996 schema=sa_schema.BLANK_SCHEMA,
997 _extend_on=_extend_on,
998 **reflection_options
999 )
1000 for column in referred_columns:
1001 refspec.append(".".join([referred_table, column]))
1002 if "options" in fkey_d:
1003 options = fkey_d["options"]
1004 else:
1005 options = {}
1007 table.append_constraint(
1008 sa_schema.ForeignKeyConstraint(
1009 constrained_columns,
1010 refspec,
1011 conname,
1012 link_to_name=True,
1013 **options
1014 )
1015 )
1017 _index_sort_exprs = [
1018 ("asc", operators.asc_op),
1019 ("desc", operators.desc_op),
1020 ("nulls_first", operators.nulls_first_op),
1021 ("nulls_last", operators.nulls_last_op),
1022 ]
1024 def _reflect_indexes(
1025 self,
1026 table_name,
1027 schema,
1028 table,
1029 cols_by_orig_name,
1030 include_columns,
1031 exclude_columns,
1032 reflection_options,
1033 ):
1034 # Indexes
1035 indexes = self.get_indexes(table_name, schema)
1036 for index_d in indexes:
1037 name = index_d["name"]
1038 columns = index_d["column_names"]
1039 column_sorting = index_d.get("column_sorting", {})
1040 unique = index_d["unique"]
1041 flavor = index_d.get("type", "index")
1042 dialect_options = index_d.get("dialect_options", {})
1044 duplicates = index_d.get("duplicates_constraint")
1045 if include_columns and not set(columns).issubset(include_columns):
1046 util.warn(
1047 "Omitting %s key for (%s), key covers omitted columns."
1048 % (flavor, ", ".join(columns))
1049 )
1050 continue
1051 if duplicates:
1052 continue
1053 # look for columns by orig name in cols_by_orig_name,
1054 # but support columns that are in-Python only as fallback
1055 idx_cols = []
1056 for c in columns:
1057 try:
1058 idx_col = (
1059 cols_by_orig_name[c]
1060 if c in cols_by_orig_name
1061 else table.c[c]
1062 )
1063 except KeyError:
1064 util.warn(
1065 "%s key '%s' was not located in "
1066 "columns for table '%s'" % (flavor, c, table_name)
1067 )
1068 continue
1069 c_sorting = column_sorting.get(c, ())
1070 for k, op in self._index_sort_exprs:
1071 if k in c_sorting:
1072 idx_col = op(idx_col)
1073 idx_cols.append(idx_col)
1075 sa_schema.Index(
1076 name,
1077 *idx_cols,
1078 _table=table,
1079 **dict(list(dialect_options.items()) + [("unique", unique)])
1080 )
1082 def _reflect_unique_constraints(
1083 self,
1084 table_name,
1085 schema,
1086 table,
1087 cols_by_orig_name,
1088 include_columns,
1089 exclude_columns,
1090 reflection_options,
1091 ):
1093 # Unique Constraints
1094 try:
1095 constraints = self.get_unique_constraints(table_name, schema)
1096 except NotImplementedError:
1097 # optional dialect feature
1098 return
1100 for const_d in constraints:
1101 conname = const_d["name"]
1102 columns = const_d["column_names"]
1103 duplicates = const_d.get("duplicates_index")
1104 if include_columns and not set(columns).issubset(include_columns):
1105 util.warn(
1106 "Omitting unique constraint key for (%s), "
1107 "key covers omitted columns." % ", ".join(columns)
1108 )
1109 continue
1110 if duplicates:
1111 continue
1112 # look for columns by orig name in cols_by_orig_name,
1113 # but support columns that are in-Python only as fallback
1114 constrained_cols = []
1115 for c in columns:
1116 try:
1117 constrained_col = (
1118 cols_by_orig_name[c]
1119 if c in cols_by_orig_name
1120 else table.c[c]
1121 )
1122 except KeyError:
1123 util.warn(
1124 "unique constraint key '%s' was not located in "
1125 "columns for table '%s'" % (c, table_name)
1126 )
1127 else:
1128 constrained_cols.append(constrained_col)
1129 table.append_constraint(
1130 sa_schema.UniqueConstraint(*constrained_cols, name=conname)
1131 )
1133 def _reflect_check_constraints(
1134 self,
1135 table_name,
1136 schema,
1137 table,
1138 cols_by_orig_name,
1139 include_columns,
1140 exclude_columns,
1141 reflection_options,
1142 ):
1143 try:
1144 constraints = self.get_check_constraints(table_name, schema)
1145 except NotImplementedError:
1146 # optional dialect feature
1147 return
1149 for const_d in constraints:
1150 table.append_constraint(sa_schema.CheckConstraint(**const_d))
1152 def _reflect_table_comment(
1153 self, table_name, schema, table, reflection_options
1154 ):
1155 try:
1156 comment_dict = self.get_table_comment(table_name, schema)
1157 except NotImplementedError:
1158 return
1159 else:
1160 table.comment = comment_dict.get("text", None)