summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2016-01-06 17:20:57 -0500
committerMike Bayer <mike_mp@zzzcomputing.com>2016-01-06 17:20:57 -0500
commitc8b7729338ba32a726be72b5409b4651326042e9 (patch)
tree35ec5496df037b60ba5fcc4e2cc5bda54f5bc942 /lib/sqlalchemy
parentcfb631e0897cf3a8cde67c120eed431eaa5f841d (diff)
downloadsqlalchemy-c8b7729338ba32a726be72b5409b4651326042e9.tar.gz
- Added a new entrypoint system to the engine to allow "plugins" to
be stated in the query string for a URL. Custom plugins can be written which will be given the chance up front to alter and/or consume the engine's URL and keyword arguments, and then at engine create time will be given the engine itself to allow additional modifications or event registration. Plugins are written as a subclass of :class:`.CreateEnginePlugin`; see that class for details. fixes #3536
Diffstat (limited to 'lib/sqlalchemy')
-rw-r--r--lib/sqlalchemy/dialects/__init__.py2
-rw-r--r--lib/sqlalchemy/engine/__init__.py3
-rw-r--r--lib/sqlalchemy/engine/interfaces.py105
-rw-r--r--lib/sqlalchemy/engine/strategies.py7
-rw-r--r--lib/sqlalchemy/engine/url.py10
5 files changed, 125 insertions, 2 deletions
diff --git a/lib/sqlalchemy/dialects/__init__.py b/lib/sqlalchemy/dialects/__init__.py
index d90a83809..f851a4ab8 100644
--- a/lib/sqlalchemy/dialects/__init__.py
+++ b/lib/sqlalchemy/dialects/__init__.py
@@ -43,3 +43,5 @@ def _auto_fn(name):
return None
registry = util.PluginLoader("sqlalchemy.dialects", auto_fn=_auto_fn)
+
+plugins = util.PluginLoader("sqlalchemy.plugins") \ No newline at end of file
diff --git a/lib/sqlalchemy/engine/__init__.py b/lib/sqlalchemy/engine/__init__.py
index 0b0d50329..02c35d6a9 100644
--- a/lib/sqlalchemy/engine/__init__.py
+++ b/lib/sqlalchemy/engine/__init__.py
@@ -53,6 +53,7 @@ url.py
from .interfaces import (
Connectable,
+ CreateEnginePlugin,
Dialect,
ExecutionContext,
ExceptionContext,
@@ -390,7 +391,7 @@ 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.
-
+
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
diff --git a/lib/sqlalchemy/engine/interfaces.py b/lib/sqlalchemy/engine/interfaces.py
index 3bad765df..41325878c 100644
--- a/lib/sqlalchemy/engine/interfaces.py
+++ b/lib/sqlalchemy/engine/interfaces.py
@@ -781,6 +781,111 @@ class Dialect(object):
pass
+class CreateEnginePlugin(object):
+ """A set of hooks intended to augment the construction of an
+ :class:`.Engine` object based on entrypoint names in a URL.
+
+ The purpose of :class:`.CreateEnginePlugin` is to allow third-party
+ systems to apply engine, pool and dialect level event listeners without
+ the need for the target application to be modified; instead, the plugin
+ names can be added to the database URL. Target applications for
+ :class:`.CreateEnginePlugin` include:
+
+ * connection and SQL performance tools, e.g. which use events to track
+ number of checkouts and/or time spent with statements
+
+ * connectivity plugins such as proxies
+
+ Plugins are registered using entry points in a similar way as that
+ of dialects::
+
+ entry_points={
+ 'sqlalchemy.plugins': [
+ 'myplugin = myapp.plugins:MyPlugin'
+ ]
+
+ A plugin that uses the above names would be invoked from a database
+ URL as in::
+
+ from sqlalchemy import create_engine
+
+ engine = create_engine(
+ "mysql+pymysql://scott:tiger@localhost/test?plugin=myplugin")
+
+ The ``plugin`` argument supports multiple instances, so that a URL
+ may specify multiple plugins; they are loaded in the order stated
+ in the URL::
+
+ engine = create_engine(
+ "mysql+pymysql://scott:tiger@localhost/"
+ "test?plugin=plugin_one&plugin=plugin_twp&plugin=plugin_three")
+
+ A plugin can receive additional arguments from the URL string as
+ well as from the keyword arguments passed to :func:`.create_engine`.
+ The :class:`.URL` object and the keyword dictionary are passed to the
+ constructor so that these arguments can be extracted from the url's
+ :attr:`.URL.query` collection as well as from the dictionary::
+
+ class MyPlugin(CreateEnginePlugin):
+ def __init__(self, url, kwargs):
+ self.my_argument_one = url.query.pop('my_argument_one')
+ self.my_argument_two = url.query.pop('my_argument_two')
+ self.my_argument_three = kwargs.pop('my_argument_three', None)
+
+ Arguments like those illustrated above would be consumed from the
+ following::
+
+ from sqlalchemy import create_engine
+
+ engine = create_engine(
+ "mysql+pymysql://scott:tiger@localhost/"
+ "test?plugin=myplugin&my_argument_one=foo&my_argument_two=bar",
+ my_argument_three='bat')
+
+ The URL and dictionary are used for subsequent setup of the engine
+ as they are, so the plugin can modify their arguments in-place.
+ Arguments that are only understood by the plugin should be popped
+ or otherwise removed so that they aren't interpreted as erroneous
+ arguments afterwards.
+
+ When the engine creation process completes and produces the
+ :class:`.Engine` object, it is again passed to the plugin via the
+ :meth:`.CreateEnginePlugin.engine_created` hook. In this hook, additional
+ changes can be made to the engine, most typically involving setup of
+ events (e.g. those defined in :ref:`core_event_toplevel`).
+
+ .. versionadded:: 1.1
+
+ """
+ def __init__(self, url, kwargs):
+ """Contruct a new :class:`.CreateEnginePlugin`.
+
+ The plugin object is instantiated individually for each call
+ to :func:`.create_engine`. A single :class:`.Engine` will be
+ passed to the :meth:`.CreateEnginePlugin.engine_created` method
+ corresponding to this URL.
+
+ :param url: the :class:`.URL` object. The plugin should inspect
+ what it needs here as well as remove its custom arguments from the
+ :attr:`.URL.query` collection. The URL can be modified in-place
+ in any other way as well.
+ :param kwargs: The keyword arguments passed to :func`.create_engine`.
+ The plugin can read and modify this dictionary in-place, to affect
+ the ultimate arguments used to create the engine. It should
+ remove its custom arguments from the dictionary as well.
+
+ """
+ self.url = url
+
+ def engine_created(self, engine):
+ """Receive the :class:`.Engine` object when it is fully constructed.
+
+ The plugin may make additional changes to the engine, such as
+ registering engine or connection pool events.
+
+ """
+
+
class ExecutionContext(object):
"""A messenger object for a Dialect that corresponds to a single
execution.
diff --git a/lib/sqlalchemy/engine/strategies.py b/lib/sqlalchemy/engine/strategies.py
index a539ee9f7..0d0414ed1 100644
--- a/lib/sqlalchemy/engine/strategies.py
+++ b/lib/sqlalchemy/engine/strategies.py
@@ -48,6 +48,10 @@ class DefaultEngineStrategy(EngineStrategy):
# create url.URL object
u = url.make_url(name_or_url)
+ plugins = u._instantiate_plugins(kwargs)
+
+ u.query.pop('plugin', None)
+
entrypoint = u._get_entrypoint()
dialect_cls = entrypoint.get_dialect_cls(u)
@@ -169,6 +173,9 @@ class DefaultEngineStrategy(EngineStrategy):
if entrypoint is not dialect_cls:
entrypoint.engine_created(engine)
+ for plugin in plugins:
+ plugin.engine_created(engine)
+
return engine
diff --git a/lib/sqlalchemy/engine/url.py b/lib/sqlalchemy/engine/url.py
index 32e3f8a6b..9a955948a 100644
--- a/lib/sqlalchemy/engine/url.py
+++ b/lib/sqlalchemy/engine/url.py
@@ -17,7 +17,7 @@ be used directly and is also accepted directly by ``create_engine()``.
import re
from .. import exc, util
from . import Dialect
-from ..dialects import registry
+from ..dialects import registry, plugins
class URL(object):
@@ -117,6 +117,14 @@ class URL(object):
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.