1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150 |
- # engine/reflection.py
- # Copyright (C) 2005-2022 the SQLAlchemy authors and contributors
- # <see AUTHORS file>
- #
- # This module is part of SQLAlchemy and is released under
- # the MIT License: https://www.opensource.org/licenses/mit-license.php
- """Provides an abstraction for obtaining database schema information.
- Usage Notes:
- Here are some general conventions when accessing the low level inspector
- methods such as get_table_names, get_columns, etc.
- 1. Inspector methods return lists of dicts in most cases for the following
- reasons:
- * They're both standard types that can be serialized.
- * Using a dict instead of a tuple allows easy expansion of attributes.
- * Using a list for the outer structure maintains order and is easy to work
- with (e.g. list comprehension [d['name'] for d in cols]).
- 2. Records that contain a name, such as the column name in a column record
- use the key 'name'. So for most return values, each record will have a
- 'name' attribute..
- """
- import contextlib
- from .base import Connectable
- from .base import Connection
- from .base import Engine
- from .. import exc
- from .. import inspection
- from .. import sql
- from .. import util
- from ..sql import operators
- from ..sql import schema as sa_schema
- from ..sql.type_api import TypeEngine
- from ..util import topological
- @util.decorator
- def cache(fn, self, con, *args, **kw):
- info_cache = kw.get("info_cache", None)
- if info_cache is None:
- return fn(self, con, *args, **kw)
- key = (
- fn.__name__,
- tuple(a for a in args if isinstance(a, util.string_types)),
- tuple((k, v) for k, v in kw.items() if k != "info_cache"),
- )
- ret = info_cache.get(key)
- if ret is None:
- ret = fn(self, con, *args, **kw)
- info_cache[key] = ret
- return ret
- @inspection._self_inspects
- class Inspector(object):
- """Performs database schema inspection.
- The Inspector acts as a proxy to the reflection methods of the
- :class:`~sqlalchemy.engine.interfaces.Dialect`, providing a
- consistent interface as well as caching support for previously
- fetched metadata.
- A :class:`_reflection.Inspector` object is usually created via the
- :func:`_sa.inspect` function, which may be passed an
- :class:`_engine.Engine`
- or a :class:`_engine.Connection`::
- from sqlalchemy import inspect, create_engine
- engine = create_engine('...')
- insp = inspect(engine)
- Where above, the :class:`~sqlalchemy.engine.interfaces.Dialect` associated
- with the engine may opt to return an :class:`_reflection.Inspector`
- subclass that
- provides additional methods specific to the dialect's target database.
- """
- @util.deprecated(
- "1.4",
- "The __init__() method on :class:`_reflection.Inspector` "
- "is deprecated and "
- "will be removed in a future release. Please use the "
- ":func:`.sqlalchemy.inspect` "
- "function on an :class:`_engine.Engine` or "
- ":class:`_engine.Connection` "
- "in order to "
- "acquire an :class:`_reflection.Inspector`.",
- )
- def __init__(self, bind):
- """Initialize a new :class:`_reflection.Inspector`.
- :param bind: a :class:`~sqlalchemy.engine.Connectable`,
- which is typically an instance of
- :class:`~sqlalchemy.engine.Engine` or
- :class:`~sqlalchemy.engine.Connection`.
- For a dialect-specific instance of :class:`_reflection.Inspector`, see
- :meth:`_reflection.Inspector.from_engine`
- """
- return self._init_legacy(bind)
- @classmethod
- def _construct(cls, init, bind):
- if hasattr(bind.dialect, "inspector"):
- cls = bind.dialect.inspector
- self = cls.__new__(cls)
- init(self, bind)
- return self
- def _init_legacy(self, bind):
- if hasattr(bind, "exec_driver_sql"):
- self._init_connection(bind)
- else:
- self._init_engine(bind)
- def _init_engine(self, engine):
- self.bind = self.engine = engine
- engine.connect().close()
- self._op_context_requires_connect = True
- self.dialect = self.engine.dialect
- self.info_cache = {}
- def _init_connection(self, connection):
- self.bind = connection
- self.engine = connection.engine
- self._op_context_requires_connect = False
- self.dialect = self.engine.dialect
- self.info_cache = {}
- @classmethod
- @util.deprecated(
- "1.4",
- "The from_engine() method on :class:`_reflection.Inspector` "
- "is deprecated and "
- "will be removed in a future release. Please use the "
- ":func:`.sqlalchemy.inspect` "
- "function on an :class:`_engine.Engine` or "
- ":class:`_engine.Connection` "
- "in order to "
- "acquire an :class:`_reflection.Inspector`.",
- )
- def from_engine(cls, bind):
- """Construct a new dialect-specific Inspector object from the given
- engine or connection.
- :param bind: a :class:`~sqlalchemy.engine.Connectable`,
- which is typically an instance of
- :class:`~sqlalchemy.engine.Engine` or
- :class:`~sqlalchemy.engine.Connection`.
- This method differs from direct a direct constructor call of
- :class:`_reflection.Inspector` in that the
- :class:`~sqlalchemy.engine.interfaces.Dialect` is given a chance to
- provide a dialect-specific :class:`_reflection.Inspector` instance,
- which may
- provide additional methods.
- See the example at :class:`_reflection.Inspector`.
- """
- return cls._construct(cls._init_legacy, bind)
- @inspection._inspects(Connectable)
- def _connectable_insp(bind):
- # this method should not be used unless some unusual case
- # has subclassed "Connectable"
- return Inspector._construct(Inspector._init_legacy, bind)
- @inspection._inspects(Engine)
- def _engine_insp(bind):
- return Inspector._construct(Inspector._init_engine, bind)
- @inspection._inspects(Connection)
- def _connection_insp(bind):
- return Inspector._construct(Inspector._init_connection, bind)
- @contextlib.contextmanager
- def _operation_context(self):
- """Return a context that optimizes for multiple operations on a single
- transaction.
- This essentially allows connect()/close() to be called if we detected
- that we're against an :class:`_engine.Engine` and not a
- :class:`_engine.Connection`.
- """
- if self._op_context_requires_connect:
- conn = self.bind.connect()
- else:
- conn = self.bind
- try:
- yield conn
- finally:
- if self._op_context_requires_connect:
- conn.close()
- @contextlib.contextmanager
- def _inspection_context(self):
- """Return an :class:`_reflection.Inspector`
- from this one that will run all
- operations on a single connection.
- """
- with self._operation_context() as conn:
- sub_insp = self._construct(self.__class__._init_connection, conn)
- sub_insp.info_cache = self.info_cache
- yield sub_insp
- @property
- def default_schema_name(self):
- """Return the default schema name presented by the dialect
- for the current engine's database user.
- E.g. this is typically ``public`` for PostgreSQL and ``dbo``
- for SQL Server.
- """
- return self.dialect.default_schema_name
- def get_schema_names(self):
- """Return all schema names."""
- if hasattr(self.dialect, "get_schema_names"):
- with self._operation_context() as conn:
- return self.dialect.get_schema_names(
- conn, info_cache=self.info_cache
- )
- return []
- def get_table_names(self, schema=None):
- """Return all table names in referred to within a particular schema.
- The names are expected to be real tables only, not views.
- Views are instead returned using the
- :meth:`_reflection.Inspector.get_view_names`
- method.
- :param schema: Schema name. If ``schema`` is left at ``None``, the
- database's default schema is
- used, else the named schema is searched. If the database does not
- support named schemas, behavior is undefined if ``schema`` is not
- passed as ``None``. For special quoting, use :class:`.quoted_name`.
- .. seealso::
- :meth:`_reflection.Inspector.get_sorted_table_and_fkc_names`
- :attr:`_schema.MetaData.sorted_tables`
- """
- with self._operation_context() as conn:
- return self.dialect.get_table_names(
- conn, schema, info_cache=self.info_cache
- )
- def has_table(self, table_name, schema=None):
- """Return True if the backend has a table of the given name.
- :param table_name: name of the table to check
- :param schema: schema name to query, if not the default schema.
- .. versionadded:: 1.4 - the :meth:`.Inspector.has_table` method
- replaces the :meth:`_engine.Engine.has_table` method.
- """
- # TODO: info_cache?
- with self._operation_context() as conn:
- return self.dialect.has_table(conn, table_name, schema)
- def has_sequence(self, sequence_name, schema=None):
- """Return True if the backend has a table of the given name.
- :param sequence_name: name of the table to check
- :param schema: schema name to query, if not the default schema.
- .. versionadded:: 1.4
- """
- # TODO: info_cache?
- with self._operation_context() as conn:
- return self.dialect.has_sequence(conn, sequence_name, schema)
- def get_sorted_table_and_fkc_names(self, schema=None):
- """Return dependency-sorted table and foreign key constraint names in
- referred to within a particular schema.
- This will yield 2-tuples of
- ``(tablename, [(tname, fkname), (tname, fkname), ...])``
- consisting of table names in CREATE order grouped with the foreign key
- constraint names that are not detected as belonging to a cycle.
- The final element
- will be ``(None, [(tname, fkname), (tname, fkname), ..])``
- which will consist of remaining
- foreign key constraint names that would require a separate CREATE
- step after-the-fact, based on dependencies between tables.
- .. versionadded:: 1.0.-
- .. seealso::
- :meth:`_reflection.Inspector.get_table_names`
- :func:`.sort_tables_and_constraints` - similar method which works
- with an already-given :class:`_schema.MetaData`.
- """
- with self._operation_context() as conn:
- tnames = self.dialect.get_table_names(
- conn, schema, info_cache=self.info_cache
- )
- tuples = set()
- remaining_fkcs = set()
- fknames_for_table = {}
- for tname in tnames:
- fkeys = self.get_foreign_keys(tname, schema)
- fknames_for_table[tname] = set([fk["name"] for fk in fkeys])
- for fkey in fkeys:
- if tname != fkey["referred_table"]:
- tuples.add((fkey["referred_table"], tname))
- try:
- candidate_sort = list(topological.sort(tuples, tnames))
- except exc.CircularDependencyError as err:
- for edge in err.edges:
- tuples.remove(edge)
- remaining_fkcs.update(
- (edge[1], fkc) for fkc in fknames_for_table[edge[1]]
- )
- candidate_sort = list(topological.sort(tuples, tnames))
- return [
- (tname, fknames_for_table[tname].difference(remaining_fkcs))
- for tname in candidate_sort
- ] + [(None, list(remaining_fkcs))]
- def get_temp_table_names(self):
- """Return a list of temporary table names for the current bind.
- This method is unsupported by most dialects; currently
- only SQLite implements it.
- .. versionadded:: 1.0.0
- """
- with self._operation_context() as conn:
- return self.dialect.get_temp_table_names(
- conn, info_cache=self.info_cache
- )
- def get_temp_view_names(self):
- """Return a list of temporary view names for the current bind.
- This method is unsupported by most dialects; currently
- only SQLite implements it.
- .. versionadded:: 1.0.0
- """
- with self._operation_context() as conn:
- return self.dialect.get_temp_view_names(
- conn, info_cache=self.info_cache
- )
- def get_table_options(self, table_name, schema=None, **kw):
- """Return a dictionary of options specified when the table of the
- given name was created.
- This currently includes some options that apply to MySQL tables.
- :param table_name: string name of the table. For special quoting,
- use :class:`.quoted_name`.
- :param schema: string schema name; if omitted, uses the default schema
- of the database connection. For special quoting,
- use :class:`.quoted_name`.
- """
- if hasattr(self.dialect, "get_table_options"):
- with self._operation_context() as conn:
- return self.dialect.get_table_options(
- conn, table_name, schema, info_cache=self.info_cache, **kw
- )
- return {}
- def get_view_names(self, schema=None):
- """Return all view names in `schema`.
- :param schema: Optional, retrieve names from a non-default schema.
- For special quoting, use :class:`.quoted_name`.
- """
- with self._operation_context() as conn:
- return self.dialect.get_view_names(
- conn, schema, info_cache=self.info_cache
- )
- def get_sequence_names(self, schema=None):
- """Return all sequence names in `schema`.
- :param schema: Optional, retrieve names from a non-default schema.
- For special quoting, use :class:`.quoted_name`.
- """
- with self._operation_context() as conn:
- return self.dialect.get_sequence_names(
- conn, schema, info_cache=self.info_cache
- )
- def get_view_definition(self, view_name, schema=None):
- """Return definition for `view_name`.
- :param schema: Optional, retrieve names from a non-default schema.
- For special quoting, use :class:`.quoted_name`.
- """
- with self._operation_context() as conn:
- return self.dialect.get_view_definition(
- conn, view_name, schema, info_cache=self.info_cache
- )
- def get_columns(self, table_name, schema=None, **kw):
- """Return information about columns in `table_name`.
- Given a string `table_name` and an optional string `schema`, return
- column information as a list of dicts with these keys:
- * ``name`` - the column's name
- * ``type`` - the type of this column; an instance of
- :class:`~sqlalchemy.types.TypeEngine`
- * ``nullable`` - boolean flag if the column is NULL or NOT NULL
- * ``default`` - the column's server default value - this is returned
- as a string SQL expression.
- * ``autoincrement`` - indicates that the column is auto incremented -
- this is returned as a boolean or 'auto'
- * ``comment`` - (optional) the comment on the column. Only some
- dialects return this key
- * ``computed`` - (optional) when present it indicates that this column
- is computed by the database. Only some dialects return this key.
- Returned as a dict with the keys:
- * ``sqltext`` - the expression used to generate this column returned
- as a string SQL expression
- * ``persisted`` - (optional) boolean that indicates if the column is
- stored in the table
- .. versionadded:: 1.3.16 - added support for computed reflection.
- * ``identity`` - (optional) when present it indicates that this column
- is a generated always column. Only some dialects return this key.
- For a list of keywords on this dict see :class:`_schema.Identity`.
- .. versionadded:: 1.4 - added support for identity column reflection.
- * ``dialect_options`` - (optional) a dict with dialect specific options
- :param table_name: string name of the table. For special quoting,
- use :class:`.quoted_name`.
- :param schema: string schema name; if omitted, uses the default schema
- of the database connection. For special quoting,
- use :class:`.quoted_name`.
- :return: list of dictionaries, each representing the definition of
- a database column.
- """
- with self._operation_context() as conn:
- col_defs = self.dialect.get_columns(
- conn, table_name, schema, info_cache=self.info_cache, **kw
- )
- for col_def in col_defs:
- # make this easy and only return instances for coltype
- coltype = col_def["type"]
- if not isinstance(coltype, TypeEngine):
- col_def["type"] = coltype()
- return col_defs
- def get_pk_constraint(self, table_name, schema=None, **kw):
- """Return information about primary key constraint on `table_name`.
- Given a string `table_name`, and an optional string `schema`, return
- primary key information as a dictionary with these keys:
- * ``constrained_columns`` -
- a list of column names that make up the primary key
- * ``name`` -
- optional name of the primary key constraint.
- :param table_name: string name of the table. For special quoting,
- use :class:`.quoted_name`.
- :param schema: string schema name; if omitted, uses the default schema
- of the database connection. For special quoting,
- use :class:`.quoted_name`.
- """
- with self._operation_context() as conn:
- return self.dialect.get_pk_constraint(
- conn, table_name, schema, info_cache=self.info_cache, **kw
- )
- def get_foreign_keys(self, table_name, schema=None, **kw):
- """Return information about foreign_keys in `table_name`.
- Given a string `table_name`, and an optional string `schema`, return
- foreign key information as a list of dicts with these keys:
- * ``constrained_columns`` -
- a list of column names that make up the foreign key
- * ``referred_schema`` -
- the name of the referred schema
- * ``referred_table`` -
- the name of the referred table
- * ``referred_columns`` -
- a list of column names in the referred table that correspond to
- constrained_columns
- * ``name`` -
- optional name of the foreign key constraint.
- :param table_name: string name of the table. For special quoting,
- use :class:`.quoted_name`.
- :param schema: string schema name; if omitted, uses the default schema
- of the database connection. For special quoting,
- use :class:`.quoted_name`.
- """
- with self._operation_context() as conn:
- return self.dialect.get_foreign_keys(
- conn, table_name, schema, info_cache=self.info_cache, **kw
- )
- def get_indexes(self, table_name, schema=None, **kw):
- """Return information about indexes in `table_name`.
- Given a string `table_name` and an optional string `schema`, return
- index information as a list of dicts with these keys:
- * ``name`` -
- the index's name
- * ``column_names`` -
- list of column names in order
- * ``unique`` -
- boolean
- * ``column_sorting`` -
- optional dict mapping column names to tuple of sort keywords,
- which may include ``asc``, ``desc``, ``nulls_first``, ``nulls_last``.
- .. versionadded:: 1.3.5
- * ``dialect_options`` -
- dict of dialect-specific index options. May not be present
- for all dialects.
- .. versionadded:: 1.0.0
- :param table_name: string name of the table. For special quoting,
- use :class:`.quoted_name`.
- :param schema: string schema name; if omitted, uses the default schema
- of the database connection. For special quoting,
- use :class:`.quoted_name`.
- """
- with self._operation_context() as conn:
- return self.dialect.get_indexes(
- conn, table_name, schema, info_cache=self.info_cache, **kw
- )
- def get_unique_constraints(self, table_name, schema=None, **kw):
- """Return information about unique constraints in `table_name`.
- Given a string `table_name` and an optional string `schema`, return
- unique constraint information as a list of dicts with these keys:
- * ``name`` -
- the unique constraint's name
- * ``column_names`` -
- list of column names in order
- :param table_name: string name of the table. For special quoting,
- use :class:`.quoted_name`.
- :param schema: string schema name; if omitted, uses the default schema
- of the database connection. For special quoting,
- use :class:`.quoted_name`.
- """
- with self._operation_context() as conn:
- return self.dialect.get_unique_constraints(
- conn, table_name, schema, info_cache=self.info_cache, **kw
- )
- def get_table_comment(self, table_name, schema=None, **kw):
- """Return information about the table comment for ``table_name``.
- Given a string ``table_name`` and an optional string ``schema``,
- return table comment information as a dictionary with these keys:
- * ``text`` -
- text of the comment.
- Raises ``NotImplementedError`` for a dialect that does not support
- comments.
- .. versionadded:: 1.2
- """
- with self._operation_context() as conn:
- return self.dialect.get_table_comment(
- conn, table_name, schema, info_cache=self.info_cache, **kw
- )
- def get_check_constraints(self, table_name, schema=None, **kw):
- """Return information about check constraints in `table_name`.
- Given a string `table_name` and an optional string `schema`, return
- check constraint information as a list of dicts with these keys:
- * ``name`` -
- the check constraint's name
- * ``sqltext`` -
- the check constraint's SQL expression
- * ``dialect_options`` -
- may or may not be present; a dictionary with additional
- dialect-specific options for this CHECK constraint
- .. versionadded:: 1.3.8
- :param table_name: string name of the table. For special quoting,
- use :class:`.quoted_name`.
- :param schema: string schema name; if omitted, uses the default schema
- of the database connection. For special quoting,
- use :class:`.quoted_name`.
- .. versionadded:: 1.1.0
- """
- with self._operation_context() as conn:
- return self.dialect.get_check_constraints(
- conn, table_name, schema, info_cache=self.info_cache, **kw
- )
- @util.deprecated_20(
- ":meth:`_reflection.Inspector.reflecttable`",
- "The :meth:`_reflection.Inspector.reflecttable` "
- "method was renamed to "
- ":meth:`_reflection.Inspector.reflect_table`. This deprecated alias "
- "will be removed in a future release.",
- )
- def reflecttable(self, *args, **kwargs):
- "See reflect_table. This method name is deprecated"
- return self.reflect_table(*args, **kwargs)
- def reflect_table(
- self,
- table,
- include_columns,
- exclude_columns=(),
- resolve_fks=True,
- _extend_on=None,
- ):
- """Given a :class:`_schema.Table` object, load its internal
- constructs based on introspection.
- This is the underlying method used by most dialects to produce
- table reflection. Direct usage is like::
- from sqlalchemy import create_engine, MetaData, Table
- from sqlalchemy import inspect
- engine = create_engine('...')
- meta = MetaData()
- user_table = Table('user', meta)
- insp = inspect(engine)
- insp.reflect_table(user_table, None)
- .. versionchanged:: 1.4 Renamed from ``reflecttable`` to
- ``reflect_table``
- :param table: a :class:`~sqlalchemy.schema.Table` instance.
- :param include_columns: a list of string column names to include
- in the reflection process. If ``None``, all columns are reflected.
- """
- if _extend_on is not None:
- if table in _extend_on:
- return
- else:
- _extend_on.add(table)
- dialect = self.bind.dialect
- with self._operation_context() as conn:
- schema = conn.schema_for_object(table)
- table_name = table.name
- # get table-level arguments that are specifically
- # intended for reflection, e.g. oracle_resolve_synonyms.
- # these are unconditionally passed to related Table
- # objects
- reflection_options = dict(
- (k, table.dialect_kwargs.get(k))
- for k in dialect.reflection_options
- if k in table.dialect_kwargs
- )
- # reflect table options, like mysql_engine
- tbl_opts = self.get_table_options(
- table_name, schema, **table.dialect_kwargs
- )
- if tbl_opts:
- # add additional kwargs to the Table if the dialect
- # returned them
- table._validate_dialect_kwargs(tbl_opts)
- if util.py2k:
- if isinstance(schema, str):
- schema = schema.decode(dialect.encoding)
- if isinstance(table_name, str):
- table_name = table_name.decode(dialect.encoding)
- found_table = False
- cols_by_orig_name = {}
- for col_d in self.get_columns(
- table_name, schema, **table.dialect_kwargs
- ):
- found_table = True
- self._reflect_column(
- table,
- col_d,
- include_columns,
- exclude_columns,
- cols_by_orig_name,
- )
- # NOTE: support tables/views with no columns
- if not found_table and not self.has_table(table_name, schema):
- raise exc.NoSuchTableError(table_name)
- self._reflect_pk(
- table_name, schema, table, cols_by_orig_name, exclude_columns
- )
- self._reflect_fk(
- table_name,
- schema,
- table,
- cols_by_orig_name,
- exclude_columns,
- resolve_fks,
- _extend_on,
- reflection_options,
- )
- self._reflect_indexes(
- table_name,
- schema,
- table,
- cols_by_orig_name,
- include_columns,
- exclude_columns,
- reflection_options,
- )
- self._reflect_unique_constraints(
- table_name,
- schema,
- table,
- cols_by_orig_name,
- include_columns,
- exclude_columns,
- reflection_options,
- )
- self._reflect_check_constraints(
- table_name,
- schema,
- table,
- cols_by_orig_name,
- include_columns,
- exclude_columns,
- reflection_options,
- )
- self._reflect_table_comment(
- table_name, schema, table, reflection_options
- )
- def _reflect_column(
- self, table, col_d, include_columns, exclude_columns, cols_by_orig_name
- ):
- orig_name = col_d["name"]
- table.metadata.dispatch.column_reflect(self, table, col_d)
- table.dispatch.column_reflect(self, table, col_d)
- # fetch name again as column_reflect is allowed to
- # change it
- name = col_d["name"]
- if (include_columns and name not in include_columns) or (
- exclude_columns and name in exclude_columns
- ):
- return
- coltype = col_d["type"]
- col_kw = dict(
- (k, col_d[k])
- for k in [
- "nullable",
- "autoincrement",
- "quote",
- "info",
- "key",
- "comment",
- ]
- if k in col_d
- )
- if "dialect_options" in col_d:
- col_kw.update(col_d["dialect_options"])
- colargs = []
- if col_d.get("default") is not None:
- default = col_d["default"]
- if isinstance(default, sql.elements.TextClause):
- default = sa_schema.DefaultClause(default, _reflected=True)
- elif not isinstance(default, sa_schema.FetchedValue):
- default = sa_schema.DefaultClause(
- sql.text(col_d["default"]), _reflected=True
- )
- colargs.append(default)
- if "computed" in col_d:
- computed = sa_schema.Computed(**col_d["computed"])
- colargs.append(computed)
- if "identity" in col_d:
- computed = sa_schema.Identity(**col_d["identity"])
- colargs.append(computed)
- if "sequence" in col_d:
- self._reflect_col_sequence(col_d, colargs)
- cols_by_orig_name[orig_name] = col = sa_schema.Column(
- name, coltype, *colargs, **col_kw
- )
- if col.key in table.primary_key:
- col.primary_key = True
- table.append_column(col, replace_existing=True)
- def _reflect_col_sequence(self, col_d, colargs):
- if "sequence" in col_d:
- # TODO: mssql and sybase are using this.
- seq = col_d["sequence"]
- sequence = sa_schema.Sequence(seq["name"], 1, 1)
- if "start" in seq:
- sequence.start = seq["start"]
- if "increment" in seq:
- sequence.increment = seq["increment"]
- colargs.append(sequence)
- def _reflect_pk(
- self, table_name, schema, table, cols_by_orig_name, exclude_columns
- ):
- pk_cons = self.get_pk_constraint(
- table_name, schema, **table.dialect_kwargs
- )
- if pk_cons:
- pk_cols = [
- cols_by_orig_name[pk]
- for pk in pk_cons["constrained_columns"]
- if pk in cols_by_orig_name and pk not in exclude_columns
- ]
- # update pk constraint name
- table.primary_key.name = pk_cons.get("name")
- # tell the PKConstraint to re-initialize
- # its column collection
- table.primary_key._reload(pk_cols)
- def _reflect_fk(
- self,
- table_name,
- schema,
- table,
- cols_by_orig_name,
- exclude_columns,
- resolve_fks,
- _extend_on,
- reflection_options,
- ):
- fkeys = self.get_foreign_keys(
- table_name, schema, **table.dialect_kwargs
- )
- for fkey_d in fkeys:
- conname = fkey_d["name"]
- # look for columns by orig name in cols_by_orig_name,
- # but support columns that are in-Python only as fallback
- constrained_columns = [
- cols_by_orig_name[c].key if c in cols_by_orig_name else c
- for c in fkey_d["constrained_columns"]
- ]
- if exclude_columns and set(constrained_columns).intersection(
- exclude_columns
- ):
- continue
- referred_schema = fkey_d["referred_schema"]
- referred_table = fkey_d["referred_table"]
- referred_columns = fkey_d["referred_columns"]
- refspec = []
- if referred_schema is not None:
- if resolve_fks:
- sa_schema.Table(
- referred_table,
- table.metadata,
- schema=referred_schema,
- autoload_with=self.bind,
- _extend_on=_extend_on,
- **reflection_options
- )
- for column in referred_columns:
- refspec.append(
- ".".join([referred_schema, referred_table, column])
- )
- else:
- if resolve_fks:
- sa_schema.Table(
- referred_table,
- table.metadata,
- autoload_with=self.bind,
- schema=sa_schema.BLANK_SCHEMA,
- _extend_on=_extend_on,
- **reflection_options
- )
- for column in referred_columns:
- refspec.append(".".join([referred_table, column]))
- if "options" in fkey_d:
- options = fkey_d["options"]
- else:
- options = {}
- table.append_constraint(
- sa_schema.ForeignKeyConstraint(
- constrained_columns,
- refspec,
- conname,
- link_to_name=True,
- **options
- )
- )
- _index_sort_exprs = [
- ("asc", operators.asc_op),
- ("desc", operators.desc_op),
- ("nulls_first", operators.nulls_first_op),
- ("nulls_last", operators.nulls_last_op),
- ]
- def _reflect_indexes(
- self,
- table_name,
- schema,
- table,
- cols_by_orig_name,
- include_columns,
- exclude_columns,
- reflection_options,
- ):
- # Indexes
- indexes = self.get_indexes(table_name, schema)
- for index_d in indexes:
- name = index_d["name"]
- columns = index_d["column_names"]
- column_sorting = index_d.get("column_sorting", {})
- unique = index_d["unique"]
- flavor = index_d.get("type", "index")
- dialect_options = index_d.get("dialect_options", {})
- duplicates = index_d.get("duplicates_constraint")
- if include_columns and not set(columns).issubset(include_columns):
- util.warn(
- "Omitting %s key for (%s), key covers omitted columns."
- % (flavor, ", ".join(columns))
- )
- continue
- if duplicates:
- continue
- # look for columns by orig name in cols_by_orig_name,
- # but support columns that are in-Python only as fallback
- idx_cols = []
- for c in columns:
- try:
- idx_col = (
- cols_by_orig_name[c]
- if c in cols_by_orig_name
- else table.c[c]
- )
- except KeyError:
- util.warn(
- "%s key '%s' was not located in "
- "columns for table '%s'" % (flavor, c, table_name)
- )
- continue
- c_sorting = column_sorting.get(c, ())
- for k, op in self._index_sort_exprs:
- if k in c_sorting:
- idx_col = op(idx_col)
- idx_cols.append(idx_col)
- sa_schema.Index(
- name,
- *idx_cols,
- _table=table,
- **dict(list(dialect_options.items()) + [("unique", unique)])
- )
- def _reflect_unique_constraints(
- self,
- table_name,
- schema,
- table,
- cols_by_orig_name,
- include_columns,
- exclude_columns,
- reflection_options,
- ):
- # Unique Constraints
- try:
- constraints = self.get_unique_constraints(table_name, schema)
- except NotImplementedError:
- # optional dialect feature
- return
- for const_d in constraints:
- conname = const_d["name"]
- columns = const_d["column_names"]
- duplicates = const_d.get("duplicates_index")
- if include_columns and not set(columns).issubset(include_columns):
- util.warn(
- "Omitting unique constraint key for (%s), "
- "key covers omitted columns." % ", ".join(columns)
- )
- continue
- if duplicates:
- continue
- # look for columns by orig name in cols_by_orig_name,
- # but support columns that are in-Python only as fallback
- constrained_cols = []
- for c in columns:
- try:
- constrained_col = (
- cols_by_orig_name[c]
- if c in cols_by_orig_name
- else table.c[c]
- )
- except KeyError:
- util.warn(
- "unique constraint key '%s' was not located in "
- "columns for table '%s'" % (c, table_name)
- )
- else:
- constrained_cols.append(constrained_col)
- table.append_constraint(
- sa_schema.UniqueConstraint(*constrained_cols, name=conname)
- )
- def _reflect_check_constraints(
- self,
- table_name,
- schema,
- table,
- cols_by_orig_name,
- include_columns,
- exclude_columns,
- reflection_options,
- ):
- try:
- constraints = self.get_check_constraints(table_name, schema)
- except NotImplementedError:
- # optional dialect feature
- return
- for const_d in constraints:
- table.append_constraint(sa_schema.CheckConstraint(**const_d))
- def _reflect_table_comment(
- self, table_name, schema, table, reflection_options
- ):
- try:
- comment_dict = self.get_table_comment(table_name, schema)
- except NotImplementedError:
- return
- else:
- table.comment = comment_dict.get("text", None)
|