Updated SqlAlchemy
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
# engine/__init__.py
|
||||
# Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Michael Bayer mike_mp@zzzcomputing.com
|
||||
# Copyright (C) 2005-2017 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
|
||||
@@ -9,7 +10,7 @@
|
||||
The engine package defines the basic components used to interface
|
||||
DB-API modules with higher-level statement construction,
|
||||
connection-management, execution and result contexts. The primary
|
||||
"entry point" class into this package is the Engine and it's public
|
||||
"entry point" class into this package is the Engine and its public
|
||||
constructor ``create_engine()``.
|
||||
|
||||
This package includes:
|
||||
@@ -50,94 +51,125 @@ url.py
|
||||
within a URL.
|
||||
"""
|
||||
|
||||
# not sure what this was used for
|
||||
#import sqlalchemy.databases
|
||||
from .interfaces import (
|
||||
Connectable,
|
||||
CreateEnginePlugin,
|
||||
Dialect,
|
||||
ExecutionContext,
|
||||
ExceptionContext,
|
||||
|
||||
from sqlalchemy.engine.base import (
|
||||
# backwards compat
|
||||
Compiled,
|
||||
TypeCompiler
|
||||
)
|
||||
|
||||
from .base import (
|
||||
Connection,
|
||||
Engine,
|
||||
NestedTransaction,
|
||||
RootTransaction,
|
||||
Transaction,
|
||||
TwoPhaseTransaction,
|
||||
)
|
||||
|
||||
from .result import (
|
||||
BaseRowProxy,
|
||||
BufferedColumnResultProxy,
|
||||
BufferedColumnRow,
|
||||
BufferedRowResultProxy,
|
||||
Compiled,
|
||||
Connectable,
|
||||
Connection,
|
||||
Dialect,
|
||||
Engine,
|
||||
ExecutionContext,
|
||||
NestedTransaction,
|
||||
FullyBufferedResultProxy,
|
||||
ResultProxy,
|
||||
RootTransaction,
|
||||
RowProxy,
|
||||
Transaction,
|
||||
TwoPhaseTransaction,
|
||||
TypeCompiler
|
||||
)
|
||||
from sqlalchemy.engine import strategies
|
||||
from sqlalchemy import util
|
||||
)
|
||||
|
||||
from .util import (
|
||||
connection_memoize
|
||||
)
|
||||
|
||||
|
||||
__all__ = (
|
||||
'BufferedColumnResultProxy',
|
||||
'BufferedColumnRow',
|
||||
'BufferedRowResultProxy',
|
||||
'Compiled',
|
||||
'Connectable',
|
||||
'Connection',
|
||||
'Dialect',
|
||||
'Engine',
|
||||
'ExecutionContext',
|
||||
'NestedTransaction',
|
||||
'ResultProxy',
|
||||
'RootTransaction',
|
||||
'RowProxy',
|
||||
'Transaction',
|
||||
'TwoPhaseTransaction',
|
||||
'TypeCompiler',
|
||||
'create_engine',
|
||||
'engine_from_config',
|
||||
)
|
||||
from . import util, strategies
|
||||
|
||||
# backwards compat
|
||||
from ..sql import ddl
|
||||
|
||||
default_strategy = 'plain'
|
||||
|
||||
|
||||
def create_engine(*args, **kwargs):
|
||||
"""Create a new Engine instance.
|
||||
"""Create a new :class:`.Engine` instance.
|
||||
|
||||
The standard method of specifying the engine is via URL as the
|
||||
first positional argument, to indicate the appropriate database
|
||||
dialect and connection arguments, with additional keyword
|
||||
arguments sent as options to the dialect and resulting Engine.
|
||||
The standard calling form is to send the URL as the
|
||||
first positional argument, usually a string
|
||||
that indicates database dialect and connection arguments::
|
||||
|
||||
The URL is a string in the form
|
||||
``dialect+driver://user:password@host/dbname[?key=value..]``, where
|
||||
``dialect`` is a database name such as ``mysql``, ``oracle``,
|
||||
``postgresql``, etc., and ``driver`` the name of a DBAPI, such as
|
||||
``psycopg2``, ``pyodbc``, ``cx_oracle``, etc. Alternatively,
|
||||
|
||||
engine = create_engine("postgresql://scott:tiger@localhost/test")
|
||||
|
||||
Additional keyword arguments may then follow it which
|
||||
establish various options on the resulting :class:`.Engine`
|
||||
and its underlying :class:`.Dialect` and :class:`.Pool`
|
||||
constructs::
|
||||
|
||||
engine = create_engine("mysql://scott:tiger@hostname/dbname",
|
||||
encoding='latin1', echo=True)
|
||||
|
||||
The string form of the URL is
|
||||
``dialect[+driver]://user:password@host/dbname[?key=value..]``, where
|
||||
``dialect`` is a database name such as ``mysql``, ``oracle``,
|
||||
``postgresql``, etc., and ``driver`` the name of a DBAPI, such as
|
||||
``psycopg2``, ``pyodbc``, ``cx_oracle``, etc. Alternatively,
|
||||
the URL can be an instance of :class:`~sqlalchemy.engine.url.URL`.
|
||||
|
||||
`**kwargs` takes a wide variety of options which are routed
|
||||
towards their appropriate components. Arguments may be
|
||||
specific to the Engine, the underlying Dialect, as well as the
|
||||
Pool. Specific dialects also accept keyword arguments that
|
||||
``**kwargs`` takes a wide variety of options which are routed
|
||||
towards their appropriate components. Arguments may be specific to
|
||||
the :class:`.Engine`, the underlying :class:`.Dialect`, as well as the
|
||||
:class:`.Pool`. Specific dialects also accept keyword arguments that
|
||||
are unique to that dialect. Here, we describe the parameters
|
||||
that are common to most ``create_engine()`` usage.
|
||||
that are common to most :func:`.create_engine()` usage.
|
||||
|
||||
:param assert_unicode: Deprecated. A warning is raised in all cases when a non-Unicode
|
||||
object is passed when SQLAlchemy would coerce into an encoding
|
||||
(note: but **not** when the DBAPI handles unicode objects natively).
|
||||
To suppress or raise this warning to an
|
||||
error, use the Python warnings filter documented at:
|
||||
http://docs.python.org/library/warnings.html
|
||||
Once established, the newly resulting :class:`.Engine` will
|
||||
request a connection from the underlying :class:`.Pool` once
|
||||
:meth:`.Engine.connect` is called, or a method which depends on it
|
||||
such as :meth:`.Engine.execute` is invoked. The :class:`.Pool` in turn
|
||||
will establish the first actual DBAPI connection when this request
|
||||
is received. The :func:`.create_engine` call itself does **not**
|
||||
establish any actual DBAPI connections directly.
|
||||
|
||||
.. seealso::
|
||||
|
||||
:doc:`/core/engines`
|
||||
|
||||
:doc:`/dialects/index`
|
||||
|
||||
:ref:`connections_toplevel`
|
||||
|
||||
:param case_sensitive=True: if False, result column names
|
||||
will match in a case-insensitive fashion, that is,
|
||||
``row['SomeColumn']``.
|
||||
|
||||
.. versionchanged:: 0.8
|
||||
By default, result row names match case-sensitively.
|
||||
In version 0.7 and prior, all matches were case-insensitive.
|
||||
|
||||
:param connect_args: a dictionary of options which will be
|
||||
passed directly to the DBAPI's ``connect()`` method as
|
||||
additional keyword arguments.
|
||||
additional keyword arguments. See the example
|
||||
at :ref:`custom_dbapi_args`.
|
||||
|
||||
:param convert_unicode=False: if set to True, all
|
||||
String/character based types will convert Unicode values to raw
|
||||
byte values going into the database, and all raw byte values to
|
||||
Python Unicode coming out in result sets. This is an
|
||||
engine-wide method to provide unicode conversion across the
|
||||
board. For unicode conversion on a column-by-column level, use
|
||||
the ``Unicode`` column type instead, described in `types`.
|
||||
:param convert_unicode=False: if set to True, sets
|
||||
the default behavior of ``convert_unicode`` on the
|
||||
:class:`.String` type to ``True``, regardless
|
||||
of a setting of ``False`` on an individual
|
||||
:class:`.String` type, thus causing all :class:`.String`
|
||||
-based columns
|
||||
to accommodate Python ``unicode`` objects. This flag
|
||||
is useful as an engine-wide setting when using a
|
||||
DBAPI that does not natively support Python
|
||||
``unicode`` objects and raises an error when
|
||||
one is received (such as pyodbc with FreeTDS).
|
||||
|
||||
See :class:`.String` for further details on
|
||||
what this flag indicates.
|
||||
|
||||
:param creator: a callable which returns a DBAPI connection.
|
||||
This creation function will be passed to the underlying
|
||||
@@ -160,23 +192,105 @@ def create_engine(*args, **kwargs):
|
||||
:ref:`dbengine_logging` for information on how to configure logging
|
||||
directly.
|
||||
|
||||
:param encoding='utf-8': the encoding to use for all Unicode
|
||||
translations, both by engine-wide unicode conversion as well as
|
||||
the ``Unicode`` type object.
|
||||
:param encoding: Defaults to ``utf-8``. This is the string
|
||||
encoding used by SQLAlchemy for string encode/decode
|
||||
operations which occur within SQLAlchemy, **outside of
|
||||
the DBAPI.** Most modern DBAPIs feature some degree of
|
||||
direct support for Python ``unicode`` objects,
|
||||
what you see in Python 2 as a string of the form
|
||||
``u'some string'``. For those scenarios where the
|
||||
DBAPI is detected as not supporting a Python ``unicode``
|
||||
object, this encoding is used to determine the
|
||||
source/destination encoding. It is **not used**
|
||||
for those cases where the DBAPI handles unicode
|
||||
directly.
|
||||
|
||||
To properly configure a system to accommodate Python
|
||||
``unicode`` objects, the DBAPI should be
|
||||
configured to handle unicode to the greatest
|
||||
degree as is appropriate - see
|
||||
the notes on unicode pertaining to the specific
|
||||
target database in use at :ref:`dialect_toplevel`.
|
||||
|
||||
Areas where string encoding may need to be accommodated
|
||||
outside of the DBAPI include zero or more of:
|
||||
|
||||
* the values passed to bound parameters, corresponding to
|
||||
the :class:`.Unicode` type or the :class:`.String` type
|
||||
when ``convert_unicode`` is ``True``;
|
||||
* the values returned in result set columns corresponding
|
||||
to the :class:`.Unicode` type or the :class:`.String`
|
||||
type when ``convert_unicode`` is ``True``;
|
||||
* the string SQL statement passed to the DBAPI's
|
||||
``cursor.execute()`` method;
|
||||
* the string names of the keys in the bound parameter
|
||||
dictionary passed to the DBAPI's ``cursor.execute()``
|
||||
as well as ``cursor.setinputsizes()`` methods;
|
||||
* the string column names retrieved from the DBAPI's
|
||||
``cursor.description`` attribute.
|
||||
|
||||
When using Python 3, the DBAPI is required to support
|
||||
*all* of the above values as Python ``unicode`` objects,
|
||||
which in Python 3 are just known as ``str``. In Python 2,
|
||||
the DBAPI does not specify unicode behavior at all,
|
||||
so SQLAlchemy must make decisions for each of the above
|
||||
values on a per-DBAPI basis - implementations are
|
||||
completely inconsistent in their behavior.
|
||||
|
||||
:param execution_options: Dictionary execution options which will
|
||||
be applied to all connections. See
|
||||
:meth:`~sqlalchemy.engine.Connection.execution_options`
|
||||
|
||||
:param implicit_returning=True: When ``True``, a RETURNING-
|
||||
compatible construct, if available, will be used to
|
||||
fetch newly generated primary key values when a single row
|
||||
INSERT statement is emitted with no existing returning()
|
||||
clause. This applies to those backends which support RETURNING
|
||||
or a compatible construct, including PostgreSQL, Firebird, Oracle,
|
||||
Microsoft SQL Server. Set this to ``False`` to disable
|
||||
the automatic usage of RETURNING.
|
||||
|
||||
:param isolation_level: this string parameter is interpreted by various
|
||||
dialects in order to affect the transaction isolation level of the
|
||||
database connection. The parameter essentially accepts some subset of
|
||||
these string arguments: ``"SERIALIZABLE"``, ``"REPEATABLE_READ"``,
|
||||
``"READ_COMMITTED"``, ``"READ_UNCOMMITTED"`` and ``"AUTOCOMMIT"``.
|
||||
Behavior here varies per backend, and
|
||||
individual dialects should be consulted directly.
|
||||
|
||||
Note that the isolation level can also be set on a per-:class:`.Connection`
|
||||
basis as well, using the
|
||||
:paramref:`.Connection.execution_options.isolation_level`
|
||||
feature.
|
||||
|
||||
.. seealso::
|
||||
|
||||
:attr:`.Connection.default_isolation_level` - view default level
|
||||
|
||||
:paramref:`.Connection.execution_options.isolation_level`
|
||||
- set per :class:`.Connection` isolation level
|
||||
|
||||
:ref:`SQLite Transaction Isolation <sqlite_isolation_level>`
|
||||
|
||||
:ref:`PostgreSQL Transaction Isolation <postgresql_isolation_level>`
|
||||
|
||||
:ref:`MySQL Transaction Isolation <mysql_isolation_level>`
|
||||
|
||||
:ref:`session_transaction_isolation` - for the ORM
|
||||
|
||||
:param label_length=None: optional integer value which limits
|
||||
the size of dynamically generated column labels to that many
|
||||
characters. If less than 6, labels are generated as
|
||||
"_(counter)". If ``None``, the value of
|
||||
``dialect.max_identifier_length`` is used instead.
|
||||
|
||||
:param listeners: A list of one or more
|
||||
:class:`~sqlalchemy.interfaces.PoolListener` objects which will
|
||||
|
||||
:param listeners: A list of one or more
|
||||
:class:`~sqlalchemy.interfaces.PoolListener` objects which will
|
||||
receive connection pool events.
|
||||
|
||||
|
||||
:param logging_name: String identifier which will be used within
|
||||
the "name" field of logging records generated within the
|
||||
"sqlalchemy.engine" logger. Defaults to a hexstring of the
|
||||
"sqlalchemy.engine" logger. Defaults to a hexstring of the
|
||||
object's id.
|
||||
|
||||
:param max_overflow=10: the number of connections to allow in
|
||||
@@ -184,10 +298,24 @@ def create_engine(*args, **kwargs):
|
||||
opened above and beyond the pool_size setting, which defaults
|
||||
to five. this is only used with :class:`~sqlalchemy.pool.QueuePool`.
|
||||
|
||||
:param module=None: used by database implementations which
|
||||
support multiple DBAPI modules, this is a reference to a DBAPI2
|
||||
module to be used instead of the engine's default module. For
|
||||
PostgreSQL, the default is psycopg2. For Oracle, it's cx_Oracle.
|
||||
:param module=None: reference to a Python module object (the module
|
||||
itself, not its string name). Specifies an alternate DBAPI module to
|
||||
be used by the engine's dialect. Each sub-dialect references a
|
||||
specific DBAPI which will be imported before first connect. This
|
||||
parameter causes the import to be bypassed, and the given module to
|
||||
be used instead. Can be used for testing of DBAPIs as well as to
|
||||
inject "mock" DBAPI implementations into the :class:`.Engine`.
|
||||
|
||||
:param paramstyle=None: The `paramstyle <http://legacy.python.org/dev/peps/pep-0249/#paramstyle>`_
|
||||
to use when rendering bound parameters. This style defaults to the
|
||||
one recommended by the DBAPI itself, which is retrieved from the
|
||||
``.paramstyle`` attribute of the DBAPI. However, most DBAPIs accept
|
||||
more than one paramstyle, and in particular it may be desirable
|
||||
to change a "named" paramstyle into a "positional" one, or vice versa.
|
||||
When this attribute is passed, it should be one of the values
|
||||
``"qmark"``, ``"numeric"``, ``"named"``, ``"format"`` or
|
||||
``"pyformat"``, and should correspond to a parameter style known
|
||||
to be supported by the DBAPI in use.
|
||||
|
||||
:param pool=None: an already-constructed instance of
|
||||
:class:`~sqlalchemy.pool.Pool`, such as a
|
||||
@@ -195,7 +323,7 @@ def create_engine(*args, **kwargs):
|
||||
pool will be used directly as the underlying connection pool
|
||||
for the engine, bypassing whatever connection parameters are
|
||||
present in the URL argument. For information on constructing
|
||||
connection pools manually, see `pooling`.
|
||||
connection pools manually, see :ref:`pooling_toplevel`.
|
||||
|
||||
:param poolclass=None: a :class:`~sqlalchemy.pool.Pool`
|
||||
subclass, which will be used to create a connection pool
|
||||
@@ -205,70 +333,102 @@ def create_engine(*args, **kwargs):
|
||||
of pool to be used.
|
||||
|
||||
:param pool_logging_name: String identifier which will be used within
|
||||
the "name" field of logging records generated within the
|
||||
"sqlalchemy.pool" logger. Defaults to a hexstring of the object's
|
||||
the "name" field of logging records generated within the
|
||||
"sqlalchemy.pool" logger. Defaults to a hexstring of the object's
|
||||
id.
|
||||
|
||||
:param pool_size=5: the number of connections to keep open
|
||||
inside the connection pool. This used with :class:`~sqlalchemy.pool.QueuePool` as
|
||||
well as :class:`~sqlalchemy.pool.SingletonThreadPool`.
|
||||
inside the connection pool. This used with
|
||||
:class:`~sqlalchemy.pool.QueuePool` as
|
||||
well as :class:`~sqlalchemy.pool.SingletonThreadPool`. With
|
||||
:class:`~sqlalchemy.pool.QueuePool`, a ``pool_size`` setting
|
||||
of 0 indicates no limit; to disable pooling, set ``poolclass`` to
|
||||
:class:`~sqlalchemy.pool.NullPool` instead.
|
||||
|
||||
:param pool_recycle=-1: this setting causes the pool to recycle
|
||||
connections after the given number of seconds has passed. It
|
||||
defaults to -1, or no timeout. For example, setting to 3600
|
||||
means connections will be recycled after one hour. Note that
|
||||
MySQL in particular will ``disconnect automatically`` if no
|
||||
MySQL in particular will disconnect automatically if no
|
||||
activity is detected on a connection for eight hours (although
|
||||
this is configurable with the MySQLDB connection itself and the
|
||||
server configuration as well).
|
||||
|
||||
:param pool_reset_on_return='rollback': set the "reset on return"
|
||||
behavior of the pool, which is whether ``rollback()``,
|
||||
``commit()``, or nothing is called upon connections
|
||||
being returned to the pool. See the docstring for
|
||||
``reset_on_return`` at :class:`.Pool`.
|
||||
|
||||
.. versionadded:: 0.7.6
|
||||
|
||||
:param pool_timeout=30: number of seconds to wait before giving
|
||||
up on getting a connection from the pool. This is only used
|
||||
with :class:`~sqlalchemy.pool.QueuePool`.
|
||||
|
||||
:param strategy='plain': used to invoke alternate :class:`~sqlalchemy.engine.base.Engine.`
|
||||
implementations. Currently available is the ``threadlocal``
|
||||
strategy, which is described in :ref:`threadlocal_strategy`.
|
||||
|
||||
:param strategy='plain': selects alternate engine implementations.
|
||||
Currently available are:
|
||||
|
||||
* the ``threadlocal`` strategy, which is described in
|
||||
:ref:`threadlocal_strategy`;
|
||||
* the ``mock`` strategy, which dispatches all statement
|
||||
execution to a function passed as the argument ``executor``.
|
||||
See `example in the FAQ
|
||||
<http://docs.sqlalchemy.org/en/latest/faq/metadata_schema.html#how-can-i-get-the-create-table-drop-table-output-as-a-string>`_.
|
||||
|
||||
:param executor=None: a function taking arguments
|
||||
``(sql, *multiparams, **params)``, to which the ``mock`` strategy will
|
||||
dispatch all statement execution. Used only by ``strategy='mock'``.
|
||||
|
||||
"""
|
||||
|
||||
strategy = kwargs.pop('strategy', default_strategy)
|
||||
strategy = strategies.strategies[strategy]
|
||||
return strategy.create(*args, **kwargs)
|
||||
|
||||
|
||||
def engine_from_config(configuration, prefix='sqlalchemy.', **kwargs):
|
||||
"""Create a new Engine instance using a configuration dictionary.
|
||||
|
||||
The dictionary is typically produced from a config file where keys
|
||||
are prefixed, such as sqlalchemy.url, sqlalchemy.echo, etc. The
|
||||
'prefix' argument indicates the prefix to be searched for.
|
||||
The dictionary is typically produced from a config file.
|
||||
|
||||
The keys of interest to ``engine_from_config()`` should be prefixed, e.g.
|
||||
``sqlalchemy.url``, ``sqlalchemy.echo``, etc. The 'prefix' argument
|
||||
indicates the prefix to be searched for. Each matching key (after the
|
||||
prefix is stripped) is treated as though it were the corresponding keyword
|
||||
argument to a :func:`.create_engine` call.
|
||||
|
||||
The only required key is (assuming the default prefix) ``sqlalchemy.url``,
|
||||
which provides the :ref:`database URL <database_urls>`.
|
||||
|
||||
A select set of keyword arguments will be "coerced" to their
|
||||
expected type based on string values. In a future release, this
|
||||
functionality will be expanded and include dialect-specific
|
||||
arguments.
|
||||
expected type based on string values. The set of arguments
|
||||
is extensible per-dialect using the ``engine_config_types`` accessor.
|
||||
|
||||
:param configuration: A dictionary (typically produced from a config file,
|
||||
but this is not a requirement). Items whose keys start with the value
|
||||
of 'prefix' will have that prefix stripped, and will then be passed to
|
||||
:ref:`create_engine`.
|
||||
|
||||
:param prefix: Prefix to match and then strip from keys
|
||||
in 'configuration'.
|
||||
|
||||
:param kwargs: Each keyword argument to ``engine_from_config()`` itself
|
||||
overrides the corresponding item taken from the 'configuration'
|
||||
dictionary. Keyword arguments should *not* be prefixed.
|
||||
|
||||
"""
|
||||
|
||||
opts = _coerce_config(configuration, prefix)
|
||||
opts.update(kwargs)
|
||||
url = opts.pop('url')
|
||||
return create_engine(url, **opts)
|
||||
|
||||
def _coerce_config(configuration, prefix):
|
||||
"""Convert configuration values to expected types."""
|
||||
|
||||
options = dict((key[len(prefix):], configuration[key])
|
||||
for key in configuration
|
||||
if key.startswith(prefix))
|
||||
for option, type_ in (
|
||||
('convert_unicode', bool),
|
||||
('pool_timeout', int),
|
||||
('echo', bool),
|
||||
('echo_pool', bool),
|
||||
('pool_recycle', int),
|
||||
('pool_size', int),
|
||||
('max_overflow', int),
|
||||
('pool_threadlocal', bool),
|
||||
):
|
||||
util.coerce_kw_type(options, option, type_)
|
||||
return options
|
||||
options['_coerce_config'] = True
|
||||
options.update(kwargs)
|
||||
url = options.pop('url')
|
||||
return create_engine(url, **options)
|
||||
|
||||
|
||||
__all__ = (
|
||||
'create_engine',
|
||||
'engine_from_config',
|
||||
)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,3 +1,10 @@
|
||||
# engine/reflection.py
|
||||
# Copyright (C) 2005-2017 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:
|
||||
@@ -18,11 +25,14 @@ methods such as get_table_names, get_columns, etc.
|
||||
'name' attribute..
|
||||
"""
|
||||
|
||||
import sqlalchemy
|
||||
from sqlalchemy import exc, sql
|
||||
from sqlalchemy import util
|
||||
from sqlalchemy.types import TypeEngine
|
||||
from sqlalchemy import schema as sa_schema
|
||||
from .. import exc, sql
|
||||
from ..sql import schema as sa_schema
|
||||
from .. import util
|
||||
from ..sql.type_api import TypeEngine
|
||||
from ..util import deprecated
|
||||
from ..util import topological
|
||||
from .. import inspection
|
||||
from .base import Connectable
|
||||
|
||||
|
||||
@util.decorator
|
||||
@@ -31,10 +41,14 @@ def cache(fn, self, con, *args, **kw):
|
||||
if info_cache is None:
|
||||
return fn(self, con, *args, **kw)
|
||||
key = (
|
||||
fn.__name__,
|
||||
tuple(a for a in args if isinstance(a, basestring)),
|
||||
tuple((k, v) for k, v in kw.iteritems() if isinstance(v, (basestring, int, float)))
|
||||
)
|
||||
fn.__name__,
|
||||
tuple(a for a in args if isinstance(a, util.string_types)),
|
||||
tuple((k, v) for k, v in kw.items() if
|
||||
isinstance(v,
|
||||
util.string_types + util.int_types + (float, )
|
||||
)
|
||||
)
|
||||
)
|
||||
ret = info_cache.get(key)
|
||||
if ret is None:
|
||||
ret = fn(self, con, *args, **kw)
|
||||
@@ -45,33 +59,94 @@ def cache(fn, self, con, *args, **kw):
|
||||
class Inspector(object):
|
||||
"""Performs database schema inspection.
|
||||
|
||||
The Inspector acts as a proxy to the dialects' reflection methods and
|
||||
provides higher level functions for accessing database schema information.
|
||||
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:`.Inspector` object is usually created via the
|
||||
:func:`.inspect` function::
|
||||
|
||||
from sqlalchemy import inspect, create_engine
|
||||
engine = create_engine('...')
|
||||
insp = inspect(engine)
|
||||
|
||||
The inspection method above is equivalent to using the
|
||||
:meth:`.Inspector.from_engine` method, i.e.::
|
||||
|
||||
engine = create_engine('...')
|
||||
insp = Inspector.from_engine(engine)
|
||||
|
||||
Where above, the :class:`~sqlalchemy.engine.interfaces.Dialect` may opt
|
||||
to return an :class:`.Inspector` subclass that provides additional
|
||||
methods specific to the dialect's target database.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, conn):
|
||||
"""Initialize the instance.
|
||||
def __init__(self, bind):
|
||||
"""Initialize a new :class:`.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:`.Inspector`, see
|
||||
:meth:`.Inspector.from_engine`
|
||||
|
||||
:param conn: a :class:`~sqlalchemy.engine.base.Connectable`
|
||||
"""
|
||||
# this might not be a connection, it could be an engine.
|
||||
self.bind = bind
|
||||
|
||||
self.conn = conn
|
||||
# set the engine
|
||||
if hasattr(conn, 'engine'):
|
||||
self.engine = conn.engine
|
||||
if hasattr(bind, 'engine'):
|
||||
self.engine = bind.engine
|
||||
else:
|
||||
self.engine = conn
|
||||
self.engine = bind
|
||||
|
||||
if self.engine is bind:
|
||||
# if engine, ensure initialized
|
||||
bind.connect().close()
|
||||
|
||||
self.dialect = self.engine.dialect
|
||||
self.info_cache = {}
|
||||
|
||||
@classmethod
|
||||
def from_engine(cls, engine):
|
||||
if hasattr(engine.dialect, 'inspector'):
|
||||
return engine.dialect.inspector(engine)
|
||||
return Inspector(engine)
|
||||
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:`.Inspector` in that the
|
||||
:class:`~sqlalchemy.engine.interfaces.Dialect` is given a chance to
|
||||
provide a dialect-specific :class:`.Inspector` instance, which may
|
||||
provide additional methods.
|
||||
|
||||
See the example at :class:`.Inspector`.
|
||||
|
||||
"""
|
||||
if hasattr(bind.dialect, 'inspector'):
|
||||
return bind.dialect.inspector(bind)
|
||||
return Inspector(bind)
|
||||
|
||||
@inspection._inspects(Connectable)
|
||||
def _insp(bind):
|
||||
return Inspector.from_engine(bind)
|
||||
|
||||
@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):
|
||||
@@ -79,70 +154,185 @@ class Inspector(object):
|
||||
"""
|
||||
|
||||
if hasattr(self.dialect, 'get_schema_names'):
|
||||
return self.dialect.get_schema_names(self.conn,
|
||||
info_cache=self.info_cache)
|
||||
return self.dialect.get_schema_names(self.bind,
|
||||
info_cache=self.info_cache)
|
||||
return []
|
||||
|
||||
def get_table_names(self, schema=None, order_by=None):
|
||||
"""Return all table names in `schema`.
|
||||
"""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:`.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`.
|
||||
|
||||
:param schema: Optional, retrieve names from a non-default schema.
|
||||
:param order_by: Optional, may be the string "foreign_key" to sort
|
||||
the result on foreign key dependencies.
|
||||
the result on foreign key dependencies. Does not automatically
|
||||
resolve cycles, and will raise :class:`.CircularDependencyError`
|
||||
if cycles exist.
|
||||
|
||||
.. deprecated:: 1.0.0 - see
|
||||
:meth:`.Inspector.get_sorted_table_and_fkc_names` for a version
|
||||
of this which resolves foreign key cycles between tables
|
||||
automatically.
|
||||
|
||||
.. versionchanged:: 0.8 the "foreign_key" sorting sorts tables
|
||||
in order of dependee to dependent; that is, in creation
|
||||
order, rather than in drop order. This is to maintain
|
||||
consistency with similar features such as
|
||||
:attr:`.MetaData.sorted_tables` and :func:`.util.sort_tables`.
|
||||
|
||||
.. seealso::
|
||||
|
||||
:meth:`.Inspector.get_sorted_table_and_fkc_names`
|
||||
|
||||
:attr:`.MetaData.sorted_tables`
|
||||
|
||||
This should probably not return view names or maybe it should return
|
||||
them with an indicator t or v.
|
||||
"""
|
||||
|
||||
if hasattr(self.dialect, 'get_table_names'):
|
||||
tnames = self.dialect.get_table_names(self.conn,
|
||||
schema,
|
||||
info_cache=self.info_cache)
|
||||
tnames = self.dialect.get_table_names(
|
||||
self.bind, schema, info_cache=self.info_cache)
|
||||
else:
|
||||
tnames = self.engine.table_names(schema)
|
||||
if order_by == 'foreign_key':
|
||||
ordered_tnames = tnames[:]
|
||||
# Order based on foreign key dependencies.
|
||||
tuples = []
|
||||
for tname in tnames:
|
||||
table_pos = tnames.index(tname)
|
||||
fkeys = self.get_foreign_keys(tname, schema)
|
||||
for fkey in fkeys:
|
||||
rtable = fkey['referred_table']
|
||||
if rtable in ordered_tnames:
|
||||
ref_pos = ordered_tnames.index(rtable)
|
||||
# Make sure it's lower in the list than anything it
|
||||
# references.
|
||||
if table_pos > ref_pos:
|
||||
ordered_tnames.pop(table_pos) # rtable moves up 1
|
||||
# insert just below rtable
|
||||
ordered_tnames.index(ref_pos, tname)
|
||||
tnames = ordered_tnames
|
||||
for fkey in self.get_foreign_keys(tname, schema):
|
||||
if tname != fkey['referred_table']:
|
||||
tuples.append((fkey['referred_table'], tname))
|
||||
tnames = list(topological.sort(tuples, tnames))
|
||||
return tnames
|
||||
|
||||
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:`.Inspector.get_table_names`
|
||||
|
||||
:func:`.sort_tables_and_constraints` - similar method which works
|
||||
with an already-given :class:`.MetaData`.
|
||||
|
||||
"""
|
||||
if hasattr(self.dialect, 'get_table_names'):
|
||||
tnames = self.dialect.get_table_names(
|
||||
self.bind, schema, info_cache=self.info_cache)
|
||||
else:
|
||||
tnames = self.engine.table_names(schema)
|
||||
|
||||
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
|
||||
|
||||
"""
|
||||
return self.dialect.get_temp_table_names(
|
||||
self.bind, 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
|
||||
|
||||
"""
|
||||
return self.dialect.get_temp_view_names(
|
||||
self.bind, 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'):
|
||||
return self.dialect.get_table_options(self.conn, table_name, schema,
|
||||
info_cache=self.info_cache,
|
||||
**kw)
|
||||
return self.dialect.get_table_options(
|
||||
self.bind, 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`.
|
||||
|
||||
"""
|
||||
|
||||
return self.dialect.get_view_names(self.conn, schema,
|
||||
info_cache=self.info_cache)
|
||||
return self.dialect.get_view_names(self.bind, 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`.
|
||||
|
||||
"""
|
||||
|
||||
return self.dialect.get_view_definition(
|
||||
self.conn, view_name, schema, info_cache=self.info_cache)
|
||||
self.bind, view_name, schema, info_cache=self.info_cache)
|
||||
|
||||
def get_columns(self, table_name, schema=None, **kw):
|
||||
"""Return information about columns in `table_name`.
|
||||
@@ -150,23 +340,31 @@ class Inspector(object):
|
||||
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
|
||||
* ``name`` - the column's name
|
||||
|
||||
type
|
||||
* ``type`` - the type of this column; an instance of
|
||||
:class:`~sqlalchemy.types.TypeEngine`
|
||||
|
||||
nullable
|
||||
boolean
|
||||
* ``nullable`` - boolean flag if the column is NULL or NOT NULL
|
||||
|
||||
default
|
||||
the column's default value
|
||||
* ``default`` - the column's server default value - this is returned
|
||||
as a string SQL expression.
|
||||
|
||||
* ``attrs`` - dict containing optional column attributes
|
||||
|
||||
: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.
|
||||
|
||||
attrs
|
||||
dict containing optional column attributes
|
||||
"""
|
||||
|
||||
col_defs = self.dialect.get_columns(self.conn, table_name, schema,
|
||||
col_defs = self.dialect.get_columns(self.bind, table_name, schema,
|
||||
info_cache=self.info_cache,
|
||||
**kw)
|
||||
for col_def in col_defs:
|
||||
@@ -176,6 +374,8 @@ class Inspector(object):
|
||||
col_def['type'] = coltype()
|
||||
return col_defs
|
||||
|
||||
@deprecated('0.7', 'Call to deprecated method get_primary_keys.'
|
||||
' Use get_pk_constraint instead.')
|
||||
def get_primary_keys(self, table_name, schema=None, **kw):
|
||||
"""Return information about primary keys in `table_name`.
|
||||
|
||||
@@ -183,12 +383,34 @@ class Inspector(object):
|
||||
primary key information as a list of column names.
|
||||
"""
|
||||
|
||||
pkeys = self.dialect.get_primary_keys(self.conn, table_name, schema,
|
||||
return self.dialect.get_pk_constraint(self.bind, table_name, schema,
|
||||
info_cache=self.info_cache,
|
||||
**kw)['constrained_columns']
|
||||
|
||||
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`.
|
||||
|
||||
"""
|
||||
return self.dialect.get_pk_constraint(self.bind, table_name, schema,
|
||||
info_cache=self.info_cache,
|
||||
**kw)
|
||||
|
||||
return pkeys
|
||||
|
||||
def get_foreign_keys(self, table_name, schema=None, **kw):
|
||||
"""Return information about foreign_keys in `table_name`.
|
||||
|
||||
@@ -208,15 +430,21 @@ class Inspector(object):
|
||||
a list of column names in the referred table that correspond to
|
||||
constrained_columns
|
||||
|
||||
\**kw
|
||||
other options passed to the dialect's get_foreign_keys() method.
|
||||
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`.
|
||||
|
||||
"""
|
||||
|
||||
fk_defs = self.dialect.get_foreign_keys(self.conn, table_name, schema,
|
||||
info_cache=self.info_cache,
|
||||
**kw)
|
||||
return fk_defs
|
||||
return self.dialect.get_foreign_keys(self.bind, 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`.
|
||||
@@ -232,104 +460,261 @@ class Inspector(object):
|
||||
|
||||
unique
|
||||
boolean
|
||||
|
||||
\**kw
|
||||
other options passed to the dialect's get_indexes() method.
|
||||
|
||||
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`.
|
||||
|
||||
"""
|
||||
|
||||
indexes = self.dialect.get_indexes(self.conn, table_name,
|
||||
schema,
|
||||
info_cache=self.info_cache, **kw)
|
||||
return indexes
|
||||
return self.dialect.get_indexes(self.bind, table_name,
|
||||
schema,
|
||||
info_cache=self.info_cache, **kw)
|
||||
|
||||
def reflecttable(self, table, include_columns):
|
||||
def get_unique_constraints(self, table_name, schema=None, **kw):
|
||||
"""Return information about unique constraints in `table_name`.
|
||||
|
||||
dialect = self.conn.dialect
|
||||
Given a string `table_name` and an optional string `schema`, return
|
||||
unique constraint information as a list of dicts with these keys:
|
||||
|
||||
# MySQL dialect does this. Applicable with other dialects?
|
||||
if hasattr(dialect, '_connection_charset') \
|
||||
and hasattr(dialect, '_adjust_casing'):
|
||||
charset = dialect._connection_charset
|
||||
dialect._adjust_casing(table)
|
||||
name
|
||||
the unique constraint's name
|
||||
|
||||
# table attributes we might need.
|
||||
reflection_options = dict(
|
||||
(k, table.kwargs.get(k)) for k in dialect.reflection_options if k in table.kwargs)
|
||||
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`.
|
||||
|
||||
.. versionadded:: 0.8.4
|
||||
|
||||
"""
|
||||
|
||||
return self.dialect.get_unique_constraints(
|
||||
self.bind, 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
|
||||
|
||||
: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
|
||||
|
||||
"""
|
||||
|
||||
return self.dialect.get_check_constraints(
|
||||
self.bind, table_name, schema, info_cache=self.info_cache, **kw)
|
||||
|
||||
def reflecttable(self, table, include_columns, exclude_columns=(),
|
||||
_extend_on=None):
|
||||
"""Given a 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.engine import reflection
|
||||
|
||||
engine = create_engine('...')
|
||||
meta = MetaData()
|
||||
user_table = Table('user', meta)
|
||||
insp = Inspector.from_engine(engine)
|
||||
insp.reflecttable(user_table, None)
|
||||
|
||||
: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
|
||||
|
||||
schema = self.bind.schema_for_object(table)
|
||||
|
||||
schema = table.schema
|
||||
table_name = table.name
|
||||
|
||||
# apply table options
|
||||
tbl_opts = self.get_table_options(table_name, schema, **table.kwargs)
|
||||
# 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:
|
||||
table.kwargs.update(tbl_opts)
|
||||
# add additional kwargs to the Table if the dialect
|
||||
# returned them
|
||||
table._validate_dialect_kwargs(tbl_opts)
|
||||
|
||||
# table.kwargs will need to be passed to each reflection method. Make
|
||||
# sure keywords are strings.
|
||||
tblkw = table.kwargs.copy()
|
||||
for (k, v) in tblkw.items():
|
||||
del tblkw[k]
|
||||
tblkw[str(k)] = v
|
||||
if util.py2k:
|
||||
if isinstance(schema, str):
|
||||
schema = schema.decode(dialect.encoding)
|
||||
if isinstance(table_name, str):
|
||||
table_name = table_name.decode(dialect.encoding)
|
||||
|
||||
# Py2K
|
||||
if isinstance(schema, str):
|
||||
schema = schema.decode(dialect.encoding)
|
||||
if isinstance(table_name, str):
|
||||
table_name = table_name.decode(dialect.encoding)
|
||||
# end Py2K
|
||||
|
||||
# columns
|
||||
found_table = False
|
||||
for col_d in self.get_columns(table_name, schema, **tblkw):
|
||||
found_table = True
|
||||
name = col_d['name']
|
||||
if include_columns and name not in include_columns:
|
||||
continue
|
||||
cols_by_orig_name = {}
|
||||
|
||||
coltype = col_d['type']
|
||||
col_kw = {
|
||||
'nullable':col_d['nullable'],
|
||||
}
|
||||
if 'autoincrement' in col_d:
|
||||
col_kw['autoincrement'] = col_d['autoincrement']
|
||||
if 'quote' in col_d:
|
||||
col_kw['quote'] = col_d['quote']
|
||||
|
||||
colargs = []
|
||||
if col_d.get('default') is not None:
|
||||
# the "default" value is assumed to be a literal SQL expression,
|
||||
# so is wrapped in text() so that no quoting occurs on re-issuance.
|
||||
colargs.append(sa_schema.DefaultClause(sql.text(col_d['default'])))
|
||||
|
||||
if 'sequence' in col_d:
|
||||
# TODO: mssql, maxdb 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)
|
||||
|
||||
col = sa_schema.Column(name, coltype, *colargs, **col_kw)
|
||||
table.append_column(col)
|
||||
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)
|
||||
|
||||
# Primary keys
|
||||
primary_key_constraint = sa_schema.PrimaryKeyConstraint(*[
|
||||
table.c[pk] for pk in self.get_primary_keys(table_name, schema, **tblkw)
|
||||
if pk in table.c
|
||||
])
|
||||
self._reflect_pk(
|
||||
table_name, schema, table, cols_by_orig_name, exclude_columns)
|
||||
|
||||
table.append_constraint(primary_key_constraint)
|
||||
self._reflect_fk(
|
||||
table_name, schema, table, cols_by_orig_name,
|
||||
exclude_columns, _extend_on, reflection_options)
|
||||
|
||||
# Foreign keys
|
||||
fkeys = self.get_foreign_keys(table_name, schema, **tblkw)
|
||||
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)
|
||||
|
||||
def _reflect_column(
|
||||
self, table, col_d, include_columns,
|
||||
exclude_columns, cols_by_orig_name):
|
||||
|
||||
orig_name = col_d['name']
|
||||
|
||||
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']
|
||||
if k in col_d
|
||||
)
|
||||
|
||||
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 '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)
|
||||
|
||||
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, _extend_on, reflection_options):
|
||||
fkeys = self.get_foreign_keys(
|
||||
table_name, schema, **table.dialect_kwargs)
|
||||
for fkey_d in fkeys:
|
||||
conname = fkey_d['name']
|
||||
constrained_columns = fkey_d['constrained_columns']
|
||||
# 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']
|
||||
@@ -337,7 +722,8 @@ class Inspector(object):
|
||||
if referred_schema is not None:
|
||||
sa_schema.Table(referred_table, table.metadata,
|
||||
autoload=True, schema=referred_schema,
|
||||
autoload_with=self.conn,
|
||||
autoload_with=self.bind,
|
||||
_extend_on=_extend_on,
|
||||
**reflection_options
|
||||
)
|
||||
for column in referred_columns:
|
||||
@@ -345,26 +731,113 @@ class Inspector(object):
|
||||
[referred_schema, referred_table, column]))
|
||||
else:
|
||||
sa_schema.Table(referred_table, table.metadata, autoload=True,
|
||||
autoload_with=self.conn,
|
||||
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))
|
||||
# Indexes
|
||||
conname, link_to_name=True,
|
||||
**options))
|
||||
|
||||
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']
|
||||
unique = index_d['unique']
|
||||
flavor = index_d.get('type', 'unknown type')
|
||||
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):
|
||||
not set(columns).issubset(include_columns):
|
||||
util.warn(
|
||||
"Omitting %s KEY for (%s), key covers omitted columns." %
|
||||
"Omitting %s key for (%s), key covers omitted columns." %
|
||||
(flavor, ', '.join(columns)))
|
||||
continue
|
||||
sa_schema.Index(name, *[table.columns[c] for c in columns],
|
||||
**dict(unique=unique))
|
||||
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
|
||||
))
|
||||
else:
|
||||
idx_cols.append(idx_col)
|
||||
|
||||
sa_schema.Index(
|
||||
name, *idx_cols,
|
||||
**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))
|
||||
|
||||
@@ -1,3 +1,10 @@
|
||||
# engine/strategies.py
|
||||
# Copyright (C) 2005-2017 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
|
||||
|
||||
"""Strategies for creating new instances of Engine types.
|
||||
|
||||
These are semi-private implementation classes which provide the
|
||||
@@ -11,18 +18,19 @@ New strategies can be added via new ``EngineStrategy`` classes.
|
||||
from operator import attrgetter
|
||||
|
||||
from sqlalchemy.engine import base, threadlocal, url
|
||||
from sqlalchemy import util, exc
|
||||
from sqlalchemy import util, event
|
||||
from sqlalchemy import pool as poollib
|
||||
from sqlalchemy.sql import schema
|
||||
|
||||
strategies = {}
|
||||
|
||||
|
||||
class EngineStrategy(object):
|
||||
"""An adaptor that processes input arguements and produces an Engine.
|
||||
"""An adaptor that processes input arguments and produces an Engine.
|
||||
|
||||
Provides a ``create`` method that receives input arguments and
|
||||
produces an instance of base.Engine or a subclass.
|
||||
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
@@ -35,58 +43,75 @@ class EngineStrategy(object):
|
||||
|
||||
|
||||
class DefaultEngineStrategy(EngineStrategy):
|
||||
"""Base class for built-in stratgies."""
|
||||
"""Base class for built-in strategies."""
|
||||
|
||||
pool_threadlocal = False
|
||||
|
||||
def create(self, name_or_url, **kwargs):
|
||||
# create url.URL object
|
||||
u = url.make_url(name_or_url)
|
||||
|
||||
dialect_cls = u.get_dialect()
|
||||
plugins = u._instantiate_plugins(kwargs)
|
||||
|
||||
u.query.pop('plugin', None)
|
||||
|
||||
entrypoint = u._get_entrypoint()
|
||||
dialect_cls = entrypoint.get_dialect_cls(u)
|
||||
|
||||
if kwargs.pop('_coerce_config', False):
|
||||
def pop_kwarg(key, default=None):
|
||||
value = kwargs.pop(key, default)
|
||||
if key in dialect_cls.engine_config_types:
|
||||
value = dialect_cls.engine_config_types[key](value)
|
||||
return value
|
||||
else:
|
||||
pop_kwarg = kwargs.pop
|
||||
|
||||
dialect_args = {}
|
||||
# consume dialect arguments from kwargs
|
||||
for k in util.get_cls_kwargs(dialect_cls):
|
||||
if k in kwargs:
|
||||
dialect_args[k] = kwargs.pop(k)
|
||||
dialect_args[k] = pop_kwarg(k)
|
||||
|
||||
dbapi = kwargs.pop('module', None)
|
||||
if dbapi is None:
|
||||
dbapi_args = {}
|
||||
for k in util.get_func_kwargs(dialect_cls.dbapi):
|
||||
if k in kwargs:
|
||||
dbapi_args[k] = kwargs.pop(k)
|
||||
dbapi_args[k] = pop_kwarg(k)
|
||||
dbapi = dialect_cls.dbapi(**dbapi_args)
|
||||
|
||||
dialect_args['dbapi'] = dbapi
|
||||
|
||||
for plugin in plugins:
|
||||
plugin.handle_dialect_kwargs(dialect_cls, dialect_args)
|
||||
|
||||
# create dialect
|
||||
dialect = dialect_cls(**dialect_args)
|
||||
|
||||
# assemble connection arguments
|
||||
(cargs, cparams) = dialect.create_connect_args(u)
|
||||
cparams.update(kwargs.pop('connect_args', {}))
|
||||
cparams.update(pop_kwarg('connect_args', {}))
|
||||
cargs = list(cargs) # allow mutability
|
||||
|
||||
# look for existing pool or create
|
||||
pool = kwargs.pop('pool', None)
|
||||
pool = pop_kwarg('pool', None)
|
||||
if pool is None:
|
||||
def connect():
|
||||
try:
|
||||
return dialect.connect(*cargs, **cparams)
|
||||
except Exception, e:
|
||||
# Py3K
|
||||
#raise exc.DBAPIError.instance(None, None, e) from e
|
||||
# Py2K
|
||||
import sys
|
||||
raise exc.DBAPIError.instance(None, None, e), None, sys.exc_info()[2]
|
||||
# end Py2K
|
||||
|
||||
creator = kwargs.pop('creator', connect)
|
||||
def connect(connection_record=None):
|
||||
if dialect._has_events:
|
||||
for fn in dialect.dispatch.do_connect:
|
||||
connection = fn(
|
||||
dialect, connection_record, cargs, cparams)
|
||||
if connection is not None:
|
||||
return connection
|
||||
return dialect.connect(*cargs, **cparams)
|
||||
|
||||
poolclass = (kwargs.pop('poolclass', None) or
|
||||
getattr(dialect_cls, 'poolclass', poollib.QueuePool))
|
||||
pool_args = {}
|
||||
creator = pop_kwarg('creator', connect)
|
||||
|
||||
poolclass = pop_kwarg('poolclass', None)
|
||||
if poolclass is None:
|
||||
poolclass = dialect_cls.get_pool_class(u)
|
||||
pool_args = {
|
||||
'dialect': dialect
|
||||
}
|
||||
|
||||
# consume pool arguments from kwargs, translating a few of
|
||||
# the arguments
|
||||
@@ -94,12 +119,17 @@ class DefaultEngineStrategy(EngineStrategy):
|
||||
'echo': 'echo_pool',
|
||||
'timeout': 'pool_timeout',
|
||||
'recycle': 'pool_recycle',
|
||||
'use_threadlocal':'pool_threadlocal'}
|
||||
'events': 'pool_events',
|
||||
'use_threadlocal': 'pool_threadlocal',
|
||||
'reset_on_return': 'pool_reset_on_return'}
|
||||
for k in util.get_cls_kwargs(poolclass):
|
||||
tk = translate.get(k, k)
|
||||
if tk in kwargs:
|
||||
pool_args[k] = kwargs.pop(tk)
|
||||
pool_args.setdefault('use_threadlocal', self.pool_threadlocal)
|
||||
pool_args[k] = pop_kwarg(tk)
|
||||
|
||||
for plugin in plugins:
|
||||
plugin.handle_pool_kwargs(poolclass, pool_args)
|
||||
|
||||
pool = poolclass(creator, **pool_args)
|
||||
else:
|
||||
if isinstance(pool, poollib._DBProxy):
|
||||
@@ -107,15 +137,17 @@ class DefaultEngineStrategy(EngineStrategy):
|
||||
else:
|
||||
pool = pool
|
||||
|
||||
pool._dialect = dialect
|
||||
|
||||
# create engine.
|
||||
engineclass = self.engine_cls
|
||||
engine_args = {}
|
||||
for k in util.get_cls_kwargs(engineclass):
|
||||
if k in kwargs:
|
||||
engine_args[k] = kwargs.pop(k)
|
||||
engine_args[k] = pop_kwarg(k)
|
||||
|
||||
_initialize = kwargs.pop('_initialize', True)
|
||||
|
||||
|
||||
# all kwargs should be consumed
|
||||
if kwargs:
|
||||
raise TypeError(
|
||||
@@ -126,24 +158,35 @@ class DefaultEngineStrategy(EngineStrategy):
|
||||
dialect.__class__.__name__,
|
||||
pool.__class__.__name__,
|
||||
engineclass.__name__))
|
||||
|
||||
|
||||
engine = engineclass(pool, dialect, u, **engine_args)
|
||||
|
||||
if _initialize:
|
||||
do_on_connect = dialect.on_connect()
|
||||
if do_on_connect:
|
||||
def on_connect(conn, rec):
|
||||
conn = getattr(conn, '_sqla_unwrap', conn)
|
||||
def on_connect(dbapi_connection, connection_record):
|
||||
conn = getattr(
|
||||
dbapi_connection, '_sqla_unwrap', dbapi_connection)
|
||||
if conn is None:
|
||||
return
|
||||
do_on_connect(conn)
|
||||
|
||||
pool.add_listener({'first_connect': on_connect, 'connect':on_connect})
|
||||
|
||||
def first_connect(conn, rec):
|
||||
c = base.Connection(engine, connection=conn)
|
||||
|
||||
event.listen(pool, 'first_connect', on_connect)
|
||||
event.listen(pool, 'connect', on_connect)
|
||||
|
||||
def first_connect(dbapi_connection, connection_record):
|
||||
c = base.Connection(engine, connection=dbapi_connection,
|
||||
_has_events=False)
|
||||
c._execution_options = util.immutabledict()
|
||||
dialect.initialize(c)
|
||||
pool.add_listener({'first_connect':first_connect})
|
||||
event.listen(pool, 'first_connect', first_connect, once=True)
|
||||
|
||||
dialect_cls.engine_created(engine)
|
||||
if entrypoint is not dialect_cls:
|
||||
entrypoint.engine_created(engine)
|
||||
|
||||
for plugin in plugins:
|
||||
plugin.engine_created(engine)
|
||||
|
||||
return engine
|
||||
|
||||
@@ -153,15 +196,14 @@ class PlainEngineStrategy(DefaultEngineStrategy):
|
||||
|
||||
name = 'plain'
|
||||
engine_cls = base.Engine
|
||||
|
||||
|
||||
PlainEngineStrategy()
|
||||
|
||||
|
||||
class ThreadLocalEngineStrategy(DefaultEngineStrategy):
|
||||
"""Strategy for configuring an Engine with thredlocal behavior."""
|
||||
|
||||
"""Strategy for configuring an Engine with threadlocal behavior."""
|
||||
|
||||
name = 'threadlocal'
|
||||
pool_threadlocal = True
|
||||
engine_cls = threadlocal.TLEngine
|
||||
|
||||
ThreadLocalEngineStrategy()
|
||||
@@ -172,11 +214,11 @@ class MockEngineStrategy(EngineStrategy):
|
||||
|
||||
Produces a single mock Connectable object which dispatches
|
||||
statement execution to a passed-in function.
|
||||
|
||||
|
||||
"""
|
||||
|
||||
name = 'mock'
|
||||
|
||||
|
||||
def create(self, name_or_url, executor, **kwargs):
|
||||
# create url.URL object
|
||||
u = url.make_url(name_or_url)
|
||||
@@ -203,9 +245,14 @@ class MockEngineStrategy(EngineStrategy):
|
||||
dialect = property(attrgetter('_dialect'))
|
||||
name = property(lambda s: s._dialect.name)
|
||||
|
||||
schema_for_object = schema._schema_getter(None)
|
||||
|
||||
def contextual_connect(self, **kwargs):
|
||||
return self
|
||||
|
||||
def execution_options(self, **kw):
|
||||
return self
|
||||
|
||||
def compiler(self, statement, parameters, **kwargs):
|
||||
return self._dialect.compiler(
|
||||
statement, parameters, engine=self, **kwargs)
|
||||
@@ -213,13 +260,22 @@ class MockEngineStrategy(EngineStrategy):
|
||||
def create(self, entity, **kwargs):
|
||||
kwargs['checkfirst'] = False
|
||||
from sqlalchemy.engine import ddl
|
||||
|
||||
ddl.SchemaGenerator(self.dialect, self, **kwargs).traverse(entity)
|
||||
|
||||
ddl.SchemaGenerator(
|
||||
self.dialect, self, **kwargs).traverse_single(entity)
|
||||
|
||||
def drop(self, entity, **kwargs):
|
||||
kwargs['checkfirst'] = False
|
||||
from sqlalchemy.engine import ddl
|
||||
ddl.SchemaDropper(self.dialect, self, **kwargs).traverse(entity)
|
||||
ddl.SchemaDropper(
|
||||
self.dialect, self, **kwargs).traverse_single(entity)
|
||||
|
||||
def _run_visitor(self, visitorcallable, element,
|
||||
connection=None,
|
||||
**kwargs):
|
||||
kwargs['checkfirst'] = False
|
||||
visitorcallable(self.dialect, self,
|
||||
**kwargs).traverse_single(element)
|
||||
|
||||
def execute(self, object, *multiparams, **params):
|
||||
raise NotImplementedError()
|
||||
|
||||
@@ -1,23 +1,33 @@
|
||||
# engine/threadlocal.py
|
||||
# Copyright (C) 2005-2017 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 a thread-local transactional wrapper around the root Engine class.
|
||||
|
||||
The ``threadlocal`` module is invoked when using the ``strategy="threadlocal"`` flag
|
||||
with :func:`~sqlalchemy.engine.create_engine`. This module is semi-private and is
|
||||
invoked automatically when the threadlocal engine strategy is used.
|
||||
The ``threadlocal`` module is invoked when using the
|
||||
``strategy="threadlocal"`` flag with :func:`~sqlalchemy.engine.create_engine`.
|
||||
This module is semi-private and is invoked automatically when the threadlocal
|
||||
engine strategy is used.
|
||||
"""
|
||||
|
||||
from sqlalchemy import util
|
||||
from sqlalchemy.engine import base
|
||||
from .. import util
|
||||
from . import base
|
||||
import weakref
|
||||
|
||||
|
||||
class TLConnection(base.Connection):
|
||||
|
||||
def __init__(self, *arg, **kw):
|
||||
super(TLConnection, self).__init__(*arg, **kw)
|
||||
self.__opencount = 0
|
||||
|
||||
|
||||
def _increment_connect(self):
|
||||
self.__opencount += 1
|
||||
return self
|
||||
|
||||
|
||||
def close(self):
|
||||
if self.__opencount == 1:
|
||||
base.Connection.close(self)
|
||||
@@ -27,70 +37,95 @@ class TLConnection(base.Connection):
|
||||
self.__opencount = 0
|
||||
base.Connection.close(self)
|
||||
|
||||
|
||||
class TLEngine(base.Engine):
|
||||
"""An Engine that includes support for thread-local managed transactions."""
|
||||
|
||||
class TLEngine(base.Engine):
|
||||
"""An Engine that includes support for thread-local managed
|
||||
transactions.
|
||||
|
||||
"""
|
||||
_tl_connection_cls = TLConnection
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(TLEngine, self).__init__(*args, **kwargs)
|
||||
self._connections = util.threading.local()
|
||||
proxy = kwargs.get('proxy')
|
||||
if proxy:
|
||||
self.TLConnection = base._proxy_connection_cls(TLConnection, proxy)
|
||||
else:
|
||||
self.TLConnection = TLConnection
|
||||
|
||||
def contextual_connect(self, **kw):
|
||||
if not hasattr(self._connections, 'conn'):
|
||||
connection = None
|
||||
else:
|
||||
connection = self._connections.conn()
|
||||
|
||||
|
||||
if connection is None or connection.closed:
|
||||
# guards against pool-level reapers, if desired.
|
||||
# or not connection.connection.is_valid:
|
||||
connection = self.TLConnection(self, self.pool.connect(), **kw)
|
||||
self._connections.conn = conn = weakref.ref(connection)
|
||||
|
||||
connection = self._tl_connection_cls(
|
||||
self,
|
||||
self._wrap_pool_connect(
|
||||
self.pool.connect, connection),
|
||||
**kw)
|
||||
self._connections.conn = weakref.ref(connection)
|
||||
|
||||
return connection._increment_connect()
|
||||
|
||||
|
||||
def begin_twophase(self, xid=None):
|
||||
if not hasattr(self._connections, 'trans'):
|
||||
self._connections.trans = []
|
||||
self._connections.trans.append(self.contextual_connect().begin_twophase(xid=xid))
|
||||
self._connections.trans.append(
|
||||
self.contextual_connect().begin_twophase(xid=xid))
|
||||
return self
|
||||
|
||||
def begin_nested(self):
|
||||
if not hasattr(self._connections, 'trans'):
|
||||
self._connections.trans = []
|
||||
self._connections.trans.append(self.contextual_connect().begin_nested())
|
||||
|
||||
self._connections.trans.append(
|
||||
self.contextual_connect().begin_nested())
|
||||
return self
|
||||
|
||||
def begin(self):
|
||||
if not hasattr(self._connections, 'trans'):
|
||||
self._connections.trans = []
|
||||
self._connections.trans.append(self.contextual_connect().begin())
|
||||
|
||||
return self
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, type, value, traceback):
|
||||
if type is None:
|
||||
self.commit()
|
||||
else:
|
||||
self.rollback()
|
||||
|
||||
def prepare(self):
|
||||
if not hasattr(self._connections, 'trans') or \
|
||||
not self._connections.trans:
|
||||
return
|
||||
self._connections.trans[-1].prepare()
|
||||
|
||||
|
||||
def commit(self):
|
||||
if not hasattr(self._connections, 'trans') or \
|
||||
not self._connections.trans:
|
||||
return
|
||||
trans = self._connections.trans.pop(-1)
|
||||
trans.commit()
|
||||
|
||||
|
||||
def rollback(self):
|
||||
if not hasattr(self._connections, 'trans') or \
|
||||
not self._connections.trans:
|
||||
return
|
||||
trans = self._connections.trans.pop(-1)
|
||||
trans.rollback()
|
||||
|
||||
|
||||
def dispose(self):
|
||||
self._connections = util.threading.local()
|
||||
super(TLEngine, self).dispose()
|
||||
|
||||
|
||||
@property
|
||||
def closed(self):
|
||||
return not hasattr(self._connections, 'conn') or \
|
||||
self._connections.conn() is None or \
|
||||
self._connections.conn().closed
|
||||
|
||||
self._connections.conn() is None or \
|
||||
self._connections.conn().closed
|
||||
|
||||
def close(self):
|
||||
if not self.closed:
|
||||
self.contextual_connect().close()
|
||||
@@ -98,6 +133,6 @@ class TLEngine(base.Engine):
|
||||
connection._force_close()
|
||||
del self._connections.conn
|
||||
self._connections.trans = []
|
||||
|
||||
|
||||
def __repr__(self):
|
||||
return 'TLEngine(%s)' % str(self.url)
|
||||
return 'TLEngine(%r)' % self.url
|
||||
|
||||
@@ -1,13 +1,23 @@
|
||||
# engine/url.py
|
||||
# Copyright (C) 2005-2017 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 the :class:`~sqlalchemy.engine.url.URL` class which encapsulates
|
||||
information about a database connection specification.
|
||||
|
||||
The URL object is created automatically when :func:`~sqlalchemy.engine.create_engine` is called
|
||||
with a string argument; alternatively, the URL is a public-facing construct which can
|
||||
The URL object is created automatically when
|
||||
:func:`~sqlalchemy.engine.create_engine` is called with a string
|
||||
argument; alternatively, the URL is a public-facing construct which can
|
||||
be used directly and is also accepted directly by ``create_engine()``.
|
||||
"""
|
||||
|
||||
import re, cgi, sys, urllib
|
||||
from sqlalchemy import exc
|
||||
import re
|
||||
from .. import exc, util
|
||||
from . import Dialect
|
||||
from ..dialects import registry, plugins
|
||||
|
||||
|
||||
class URL(object):
|
||||
@@ -15,8 +25,8 @@ class URL(object):
|
||||
Represent the components of a URL used to connect to a database.
|
||||
|
||||
This object is suitable to be passed directly to a
|
||||
``create_engine()`` call. The fields of the URL are parsed from a
|
||||
string by the ``module-level make_url()`` function. the string
|
||||
:func:`~sqlalchemy.create_engine` call. The fields of the URL are parsed
|
||||
from a string by the :func:`.make_url` function. the string
|
||||
format of the URL is an RFC-1738-style string.
|
||||
|
||||
All initialization parameters are available as public attributes.
|
||||
@@ -53,25 +63,35 @@ class URL(object):
|
||||
self.database = database
|
||||
self.query = query or {}
|
||||
|
||||
def __str__(self):
|
||||
def __to_string__(self, hide_password=True):
|
||||
s = self.drivername + "://"
|
||||
if self.username is not None:
|
||||
s += self.username
|
||||
s += _rfc_1738_quote(self.username)
|
||||
if self.password is not None:
|
||||
s += ':' + urllib.quote_plus(self.password)
|
||||
s += ':' + ('***' if hide_password
|
||||
else _rfc_1738_quote(self.password))
|
||||
s += "@"
|
||||
if self.host is not None:
|
||||
s += self.host
|
||||
if ':' in self.host:
|
||||
s += "[%s]" % self.host
|
||||
else:
|
||||
s += self.host
|
||||
if self.port is not None:
|
||||
s += ':' + str(self.port)
|
||||
if self.database is not None:
|
||||
s += '/' + self.database
|
||||
if self.query:
|
||||
keys = self.query.keys()
|
||||
keys = list(self.query)
|
||||
keys.sort()
|
||||
s += '?' + "&".join("%s=%s" % (k, self.query[k]) for k in keys)
|
||||
return s
|
||||
|
||||
def __str__(self):
|
||||
return self.__to_string__(hide_password=False)
|
||||
|
||||
def __repr__(self):
|
||||
return self.__to_string__()
|
||||
|
||||
def __hash__(self):
|
||||
return hash(str(self))
|
||||
|
||||
@@ -85,49 +105,58 @@ class URL(object):
|
||||
self.database == other.database and \
|
||||
self.query == other.query
|
||||
|
||||
def get_backend_name(self):
|
||||
if '+' not in self.drivername:
|
||||
return self.drivername
|
||||
else:
|
||||
return self.drivername.split('+')[0]
|
||||
|
||||
def get_driver_name(self):
|
||||
if '+' not in self.drivername:
|
||||
return self.get_dialect().driver
|
||||
else:
|
||||
return self.drivername.split('+')[1]
|
||||
|
||||
def _instantiate_plugins(self, kwargs):
|
||||
plugin_names = util.to_list(self.query.get('plugin', ()))
|
||||
|
||||
return [
|
||||
plugins.load(plugin_name)(self, kwargs)
|
||||
for plugin_name in plugin_names
|
||||
]
|
||||
|
||||
def _get_entrypoint(self):
|
||||
"""Return the "entry point" dialect class.
|
||||
|
||||
This is normally the dialect itself except in the case when the
|
||||
returned class implements the get_dialect_cls() method.
|
||||
|
||||
"""
|
||||
if '+' not in self.drivername:
|
||||
name = self.drivername
|
||||
else:
|
||||
name = self.drivername.replace('+', '.')
|
||||
cls = registry.load(name)
|
||||
# check for legacy dialects that
|
||||
# would return a module with 'dialect' as the
|
||||
# actual class
|
||||
if hasattr(cls, 'dialect') and \
|
||||
isinstance(cls.dialect, type) and \
|
||||
issubclass(cls.dialect, Dialect):
|
||||
return cls.dialect
|
||||
else:
|
||||
return cls
|
||||
|
||||
def get_dialect(self):
|
||||
"""Return the SQLAlchemy database dialect class corresponding
|
||||
to this URL's driver name.
|
||||
"""
|
||||
entrypoint = self._get_entrypoint()
|
||||
dialect_cls = entrypoint.get_dialect_cls(self)
|
||||
return dialect_cls
|
||||
|
||||
try:
|
||||
if '+' in self.drivername:
|
||||
dialect, driver = self.drivername.split('+')
|
||||
else:
|
||||
dialect, driver = self.drivername, 'base'
|
||||
|
||||
module = __import__('sqlalchemy.dialects.%s' % (dialect, )).dialects
|
||||
module = getattr(module, dialect)
|
||||
module = getattr(module, driver)
|
||||
|
||||
return module.dialect
|
||||
except ImportError:
|
||||
module = self._load_entry_point()
|
||||
if module is not None:
|
||||
return module
|
||||
else:
|
||||
raise
|
||||
|
||||
def _load_entry_point(self):
|
||||
"""attempt to load this url's dialect from entry points, or return None
|
||||
if pkg_resources is not installed or there is no matching entry point.
|
||||
|
||||
Raise ImportError if the actual load fails.
|
||||
|
||||
"""
|
||||
try:
|
||||
import pkg_resources
|
||||
except ImportError:
|
||||
return None
|
||||
|
||||
for res in pkg_resources.iter_entry_points('sqlalchemy.dialects'):
|
||||
if res.name == self.drivername:
|
||||
return res.load()
|
||||
else:
|
||||
return None
|
||||
|
||||
def translate_connect_args(self, names=[], **kw):
|
||||
"""Translate url attributes into a dictionary of connection arguments.
|
||||
r"""Translate url attributes into a dictionary of connection arguments.
|
||||
|
||||
Returns attributes of this url (`host`, `database`, `username`,
|
||||
`password`, `port`) as a plain dictionary. The attribute names are
|
||||
@@ -136,8 +165,8 @@ class URL(object):
|
||||
|
||||
:param \**kw: Optional, alternate key names for url attributes.
|
||||
|
||||
:param names: Deprecated. Same purpose as the keyword-based alternate names,
|
||||
but correlates the name to the original positionally.
|
||||
:param names: Deprecated. Same purpose as the keyword-based alternate
|
||||
names, but correlates the name to the original positionally.
|
||||
"""
|
||||
|
||||
translated = {}
|
||||
@@ -153,6 +182,7 @@ class URL(object):
|
||||
translated[name] = getattr(self, sname)
|
||||
return translated
|
||||
|
||||
|
||||
def make_url(name_or_url):
|
||||
"""Given a string or unicode instance, produce a new URL instance.
|
||||
|
||||
@@ -160,25 +190,28 @@ def make_url(name_or_url):
|
||||
existing URL object is passed, just returns the object.
|
||||
"""
|
||||
|
||||
if isinstance(name_or_url, basestring):
|
||||
if isinstance(name_or_url, util.string_types):
|
||||
return _parse_rfc1738_args(name_or_url)
|
||||
else:
|
||||
return name_or_url
|
||||
|
||||
|
||||
def _parse_rfc1738_args(name):
|
||||
pattern = re.compile(r'''
|
||||
(?P<name>[\w\+]+)://
|
||||
(?:
|
||||
(?P<username>[^:/]*)
|
||||
(?::(?P<password>[^/]*))?
|
||||
(?::(?P<password>.*))?
|
||||
@)?
|
||||
(?:
|
||||
(?P<host>[^/:]*)
|
||||
(?:
|
||||
\[(?P<ipv6host>[^/]+)\] |
|
||||
(?P<ipv4host>[^/:]+)
|
||||
)?
|
||||
(?::(?P<port>[^/]*))?
|
||||
)?
|
||||
(?:/(?P<database>.*))?
|
||||
'''
|
||||
, re.X)
|
||||
''', re.X)
|
||||
|
||||
m = pattern.match(name)
|
||||
if m is not None:
|
||||
@@ -186,29 +219,43 @@ def _parse_rfc1738_args(name):
|
||||
if components['database'] is not None:
|
||||
tokens = components['database'].split('?', 2)
|
||||
components['database'] = tokens[0]
|
||||
query = (len(tokens) > 1 and dict(cgi.parse_qsl(tokens[1]))) or None
|
||||
# Py2K
|
||||
if query is not None:
|
||||
query = (
|
||||
len(tokens) > 1 and dict(util.parse_qsl(tokens[1]))) or None
|
||||
if util.py2k and query is not None:
|
||||
query = dict((k.encode('ascii'), query[k]) for k in query)
|
||||
# end Py2K
|
||||
else:
|
||||
query = None
|
||||
components['query'] = query
|
||||
|
||||
if components['password'] is not None:
|
||||
components['password'] = urllib.unquote_plus(components['password'])
|
||||
if components['username'] is not None:
|
||||
components['username'] = _rfc_1738_unquote(components['username'])
|
||||
|
||||
if components['password'] is not None:
|
||||
components['password'] = _rfc_1738_unquote(components['password'])
|
||||
|
||||
ipv4host = components.pop('ipv4host')
|
||||
ipv6host = components.pop('ipv6host')
|
||||
components['host'] = ipv4host or ipv6host
|
||||
name = components.pop('name')
|
||||
return URL(name, **components)
|
||||
else:
|
||||
raise exc.ArgumentError(
|
||||
"Could not parse rfc1738 URL from string '%s'" % name)
|
||||
|
||||
|
||||
def _rfc_1738_quote(text):
|
||||
return re.sub(r'[:@/]', lambda m: "%%%X" % ord(m.group(0)), text)
|
||||
|
||||
|
||||
def _rfc_1738_unquote(text):
|
||||
return util.unquote(text)
|
||||
|
||||
|
||||
def _parse_keyvalue_args(name):
|
||||
m = re.match( r'(\w+)://(.*)', name)
|
||||
m = re.match(r'(\w+)://(.*)', name)
|
||||
if m is not None:
|
||||
(name, args) = m.group(1, 2)
|
||||
opts = dict( cgi.parse_qsl( args ) )
|
||||
opts = dict(util.parse_qsl(args))
|
||||
return URL(name, *opts)
|
||||
else:
|
||||
return None
|
||||
|
||||
Reference in New Issue
Block a user