OpenHome/venv/Lib/site-packages/sqlalchemy/engine/reflection.py

1150 lines
38 KiB
Python
Raw Permalink Normal View History

2021-07-21 21:33:05 +02:00
# engine/reflection.py
# Copyright (C) 2005-2021 the SQLAlchemy authors and contributors
# <see AUTHORS file>
#
# This module is part of SQLAlchemy and is released under
# the MIT License: http://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,
)
if not found_table:
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)