]> git.ipfire.org Git - thirdparty/sqlalchemy/alembic.git/commitdiff
Add dialect_options to environment; set paramstyle=named for offline
authorMike Bayer <mike_mp@zzzcomputing.com>
Sat, 20 Jul 2019 16:54:37 +0000 (12:54 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Sun, 21 Jul 2019 16:07:30 +0000 (12:07 -0400)
Fixed bug where the double-percent logic applied to some dialects such as
psycopg2 would be rendered in ``--sql`` mode, by allowing dialect options
to be passed through to the dialect used to generate SQL and then providing
``paramstyle="named"`` so that percent signs need not be doubled.   For
users having this issue, existing env.py scripts need to add
``dialect_opts={"paramstyle": "named"}`` to their offline
context.configure().  See the ``alembic/templates/generic/env.py`` template
for an example.

Change-Id: Ia6a495704b029eaff43fb3b6df602ca667002b7a
Fixes: #562
alembic/runtime/environment.py
alembic/runtime/migration.py
alembic/templates/generic/env.py
alembic/templates/multidb/env.py
alembic/templates/pylons/env.py
alembic/testing/requirements.py
docs/build/unreleased/562.rst [new file with mode: 0644]
tests/test_environment.py

index c4d6586b4b631053628da0584974b3aca5f3eea1..b918269e3d5b4f139352f65bb4f496ea95fc3ffd 100644 (file)
@@ -289,6 +289,7 @@ class EnvironmentContext(util.ModuleClsProxy):
         connection=None,
         url=None,
         dialect_name=None,
+        dialect_opts=None,
         transactional_ddl=None,
         transaction_per_migration=False,
         output_buffer=None,
@@ -355,6 +356,11 @@ class EnvironmentContext(util.ModuleClsProxy):
          "postgresql", "mssql", etc.
          The type of dialect to be used will be derived from this if
          ``connection`` and ``url`` are not passed.
+        :param dialect_opts: dictionary of options to be passed to dialect
+         constructor.
+
+         .. versionadded:: 1.0.11
+
         :param transactional_ddl: Force the usage of "transactional"
          DDL on or off;
          this otherwise defaults to whether or not the dialect in
@@ -812,6 +818,7 @@ class EnvironmentContext(util.ModuleClsProxy):
             url=url,
             dialect_name=dialect_name,
             environment_context=self,
+            dialect_opts=dialect_opts,
             opts=opts,
         )
 
index b3a98633271531541862d245c6e419edd7bf2018..aabfc499ad44b042b9ba828cf94aeda8dd4f2930 100644 (file)
@@ -145,6 +145,7 @@ class MigrationContext(object):
         dialect_name=None,
         dialect=None,
         environment_context=None,
+        dialect_opts=None,
         opts=None,
     ):
         """Create a new :class:`.MigrationContext`.
@@ -169,6 +170,8 @@ class MigrationContext(object):
         """
         if opts is None:
             opts = {}
+        if dialect_opts is None:
+            dialect_opts = {}
 
         if connection:
             if not isinstance(connection, Connection):
@@ -176,15 +179,15 @@ class MigrationContext(object):
                     "'connection' argument to configure() is expected "
                     "to be a sqlalchemy.engine.Connection instance, "
                     "got %r" % connection,
-                    stacklevel=3
+                    stacklevel=3,
                 )
             dialect = connection.dialect
         elif url:
             url = sqla_url.make_url(url)
-            dialect = url.get_dialect()()
+            dialect = url.get_dialect()(**dialect_opts)
         elif dialect_name:
             url = sqla_url.make_url("%s://" % dialect_name)
-            dialect = url.get_dialect()()
+            dialect = url.get_dialect()(**dialect_opts)
         elif not dialect:
             raise Exception("Connection, url, or dialect_name is required.")
 
index 15cb4725951092618258f4b8e9e8cb72b8e6f26c..70518a2eef734a8fffcd787cfa397309469f8e76 100644 (file)
@@ -1,4 +1,3 @@
-
 from logging.config import fileConfig
 
 from sqlalchemy import engine_from_config
@@ -40,7 +39,10 @@ def run_migrations_offline():
     """
     url = config.get_main_option("sqlalchemy.url")
     context.configure(
-        url=url, target_metadata=target_metadata, literal_binds=True
+        url=url,
+        target_metadata=target_metadata,
+        literal_binds=True,
+        dialect_opts={"paramstyle": "named"},
     )
 
     with context.begin_transaction():
index 607efaa021330186c36347584823a19d299faba7..f1a9a9ad3e7672821e82e6b28c297ebcde5da41a 100644 (file)
@@ -1,4 +1,3 @@
-
 import logging
 from logging.config import fileConfig
 import re
@@ -73,6 +72,7 @@ def run_migrations_offline():
                 output_buffer=buffer,
                 target_metadata=target_metadata.get(name),
                 literal_binds=True,
+                dialect_opts={"paramstyle": "named"},
             )
             with context.begin_transaction():
                 context.run_migrations(engine_name=name)
index f8abf447585b855aed098e94b77f705e5c5bb26a..b2d610d707536f43dddbe4e19156b935480243e4 100644 (file)
@@ -53,6 +53,7 @@ def run_migrations_offline():
         url=meta.engine.url,
         target_metadata=target_metadata,
         literal_binds=True,
+        dialect_opts={"paramstyle": "named"},
     )
     with context.begin_transaction():
         context.run_migrations()
index cf570a5cae8ccaab272cb42bd0989702bc14d45a..b4c147beb654eae32cb59dc09071d0164f665274 100644 (file)
@@ -61,6 +61,14 @@ class SuiteRequirements(Requirements):
     def reflects_fk_options(self):
         return exclusions.closed()
 
+    @property
+    def sqlalchemy_issue_3740(self):
+        """Fixes percent sign escaping for paramstyles that don't require it"""
+        return exclusions.skip_if(
+            lambda config: not util.sqla_120,
+            "SQLAlchemy 1.2 or greater required",
+        )
+
     @property
     def sqlalchemy_12(self):
         return exclusions.skip_if(
diff --git a/docs/build/unreleased/562.rst b/docs/build/unreleased/562.rst
new file mode 100644 (file)
index 0000000..7cd8fc1
--- /dev/null
@@ -0,0 +1,12 @@
+.. change::
+    :tags: bug, commands
+    :tickets: 562
+
+    Fixed bug where the double-percent logic applied to some dialects such as
+    psycopg2 would be rendered in ``--sql`` mode, by allowing dialect options
+    to be passed through to the dialect used to generate SQL and then providing
+    ``paramstyle="named"`` so that percent signs need not be doubled.   For
+    users having this issue, existing env.py scripts need to add
+    ``dialect_opts={"paramstyle": "named"}`` to their offline
+    context.configure().  See the ``alembic/templates/generic/env.py`` template
+    for an example.
index 6109ed76cc8ad255dad468e5ec52defead6b32c9..fadab54f53ccccddb9ecea6d0fe9950a16abb18e 100644 (file)
@@ -1,8 +1,9 @@
 #!coding: utf-8
-
+from alembic import command
 from alembic.environment import EnvironmentContext
 from alembic.migration import MigrationContext
 from alembic.script import ScriptDirectory
+from alembic.testing import config
 from alembic.testing import eq_
 from alembic.testing import is_
 from alembic.testing.assertions import expect_warnings
@@ -11,6 +12,7 @@ from alembic.testing.env import _sqlite_file_db
 from alembic.testing.env import clear_staging_env
 from alembic.testing.env import staging_env
 from alembic.testing.env import write_script
+from alembic.testing.fixtures import capture_context_buffer
 from alembic.testing.fixtures import TestBase
 from alembic.testing.mock import call
 from alembic.testing.mock import MagicMock
@@ -61,6 +63,35 @@ class EnvironmentTest(TestBase):
         ctx = MigrationContext(ctx.dialect, None, {})
         is_(ctx.config, None)
 
+    @config.requirements.sqlalchemy_issue_3740
+    def test_sql_mode_parameters(self):
+        env = self._fixture()
+
+        a_rev = "arev"
+        env.script.generate_revision(a_rev, "revision a", refresh=True)
+        write_script(
+            env.script,
+            a_rev,
+            """\
+"Rev A"
+revision = '{}'
+down_revision = None
+
+from alembic import op
+
+def upgrade():
+    op.execute('''
+        do some SQL thing with a % percent sign %
+    ''')
+
+""".format(
+                a_rev
+            ),
+        )
+        with capture_context_buffer(transactional_ddl=True) as buf:
+            command.upgrade(self.cfg, "arev", sql=True)
+        assert "do some SQL thing with a % percent sign %" in buf.getvalue()
+
     def test_warning_on_passing_engine(self):
         env = self._fixture()