diff options
-rw-r--r-- | alembic/script/write_hooks.py | 28 | ||||
-rw-r--r-- | alembic/templates/async/alembic.ini.mako | 8 | ||||
-rw-r--r-- | alembic/templates/generic/alembic.ini.mako | 8 | ||||
-rw-r--r-- | alembic/templates/multidb/alembic.ini.mako | 8 | ||||
-rw-r--r-- | alembic/templates/pylons/alembic.ini.mako | 8 | ||||
-rw-r--r-- | docs/build/autogenerate.rst | 48 | ||||
-rw-r--r-- | docs/build/tutorial.rst | 8 | ||||
-rw-r--r-- | docs/build/unreleased/819.rst | 9 | ||||
-rw-r--r-- | tests/test_post_write.py | 52 |
9 files changed, 117 insertions, 60 deletions
diff --git a/alembic/script/write_hooks.py b/alembic/script/write_hooks.py index d6d1d38..27e9a48 100644 --- a/alembic/script/write_hooks.py +++ b/alembic/script/write_hooks.py @@ -1,3 +1,4 @@ +import shlex import subprocess import sys @@ -5,6 +6,8 @@ from .. import util from ..util import compat +REVISION_SCRIPT_TOKEN = "REVISION_SCRIPT_FILENAME" + _registry = {} @@ -85,6 +88,24 @@ def _run_hooks(path, hook_config): ) +def _parse_options(options_str, path): + """Parse options from a string into a list. + + Also substitutes the revision script token with the actual filename of + the revision script. + + If the revision script token doesn't occur in the options string, it is + automatically prepended. + """ + if REVISION_SCRIPT_TOKEN not in options_str: + options_str = REVISION_SCRIPT_TOKEN + " " + options_str + options_list = shlex.split(options_str) + options_list = [ + option.replace(REVISION_SCRIPT_TOKEN, path) for option in options_list + ] + return options_list + + @register("console_scripts") def console_scripts(path, options): import pkg_resources @@ -101,14 +122,15 @@ def console_scripts(path, options): ) iter_ = pkg_resources.iter_entry_points("console_scripts", entrypoint_name) impl = next(iter_) - options = options.get("options", "") + options_str = options.get("options", "") + options_list = _parse_options(options_str, path) + subprocess.run( [ sys.executable, "-c", "import %s; %s()" % (impl.module_name, ".".join((impl.module_name,) + impl.attrs)), - path, ] - + options.split() + + options_list ) diff --git a/alembic/templates/async/alembic.ini.mako b/alembic/templates/async/alembic.ini.mako index bf7e5d1..31de75a 100644 --- a/alembic/templates/async/alembic.ini.mako +++ b/alembic/templates/async/alembic.ini.mako @@ -48,10 +48,10 @@ sqlalchemy.url = driver://user:pass@localhost/dbname # detail and examples # format using "black" - use the console_scripts runner, against the "black" entrypoint -# hooks=black -# black.type=console_scripts -# black.entrypoint=black -# black.options=-l 79 +# hooks = black +# black.type = console_scripts +# black.entrypoint = black +# black.options = -l 79 REVISION_SCRIPT_FILENAME # Logging configuration [loggers] diff --git a/alembic/templates/generic/alembic.ini.mako b/alembic/templates/generic/alembic.ini.mako index bf7e5d1..31de75a 100644 --- a/alembic/templates/generic/alembic.ini.mako +++ b/alembic/templates/generic/alembic.ini.mako @@ -48,10 +48,10 @@ sqlalchemy.url = driver://user:pass@localhost/dbname # detail and examples # format using "black" - use the console_scripts runner, against the "black" entrypoint -# hooks=black -# black.type=console_scripts -# black.entrypoint=black -# black.options=-l 79 +# hooks = black +# black.type = console_scripts +# black.entrypoint = black +# black.options = -l 79 REVISION_SCRIPT_FILENAME # Logging configuration [loggers] diff --git a/alembic/templates/multidb/alembic.ini.mako b/alembic/templates/multidb/alembic.ini.mako index ec3c519..363be67 100644 --- a/alembic/templates/multidb/alembic.ini.mako +++ b/alembic/templates/multidb/alembic.ini.mako @@ -53,10 +53,10 @@ sqlalchemy.url = driver://user:pass@localhost/dbname2 # detail and examples # format using "black" - use the console_scripts runner, against the "black" entrypoint -# hooks=black -# black.type=console_scripts -# black.entrypoint=black -# black.options=-l 79 +# hooks = black +# black.type = console_scripts +# black.entrypoint = black +# black.options = -l 79 REVISION_SCRIPT_FILENAME # Logging configuration [loggers] diff --git a/alembic/templates/pylons/alembic.ini.mako b/alembic/templates/pylons/alembic.ini.mako index c37397d..b0b8fe1 100644 --- a/alembic/templates/pylons/alembic.ini.mako +++ b/alembic/templates/pylons/alembic.ini.mako @@ -45,10 +45,10 @@ prepend_sys_path = . # detail and examples # format using "black" - use the console_scripts runner, against the "black" entrypoint -# hooks=black -# black.type=console_scripts -# black.entrypoint=black -# black.options=-l 79 +# hooks = black +# black.type = console_scripts +# black.entrypoint = black +# black.options = -l 79 REVISION_SCRIPT_FILENAME pylons_config_file = ./development.ini diff --git a/docs/build/autogenerate.rst b/docs/build/autogenerate.rst index 00fd743..a5129f8 100644 --- a/docs/build/autogenerate.rst +++ b/docs/build/autogenerate.rst @@ -738,42 +738,44 @@ generated file path. Example:: # format using "black" hooks=black - black.type=console_scripts - black.entrypoint=black - black.options=-l 79 + black.type = console_scripts + black.entrypoint = black + black.options = -l 79 -Above, we configure a single post write hook that we call ``"black"``. Note -that this name is arbitrary. We then define the configuration for the -``"black"`` post write hook, which includes: +Above, we configure ``hooks`` to be a single post write hook labeled +``"black"``. Note that this label is arbitrary. We then define the +configuration for the ``"black"`` post write hook, which includes: -* ``type`` - this is the type of hook we are running. Alembic includes +* ``type`` - this is the type of hook we are running. Alembic includes a hook runner called ``"console_scripts"``, which is specifically a Python function that uses ``subprocess.run()`` to invoke a separate - Python script against the revision file. For a custom-written hook + Python script against the revision file. For a custom-written hook function, this configuration variable would refer to the name under which the custom hook was registered; see the next section for an example. * ``entrypoint`` - this part of the configuration is specific to the ``"console_scripts"`` hook runner. This is the name of the `setuptools entrypoint <https://setuptools.readthedocs.io/en/latest/pkg_resources.html#entry-points>`_ - that is used to define the console script. Within the scope of standard + that is used to define the console script. Within the scope of standard Python console scripts, this name will match the name of the shell command that is usually run for the code formatting tool, in this case ``black``. * ``options`` - this is also specific to the ``"console_scripts"`` hook runner. This is a line of command-line options that will be passed to the code formatting tool. In this case, we want to run the command - as ``black -l 79 /path/to/revision.py``. The path of the revision file - is sent as a single positional argument to the script after the options. + ``black /path/to/revision.py -l 79``. By default, the revision path is + positioned as the first argument. In order specify a different position, + we can use the ``REVISION_SCRIPT_FILENAME`` token as illustrated by the + subsequent examples. .. note:: Make sure options for the script are provided such that it will rewrite the input file **in place**. For example, when running ``autopep8``, the ``--in-place`` option should be provided:: [post_write_hooks] - hooks=autopep8 - autopep8.type=console_scripts - autopep8.entrypoint=autopep8 - autopep8.options=--in-place + hooks = autopep8 + autopep8.type = console_scripts + autopep8.entrypoint = autopep8 + autopep8.options = --in-place REVISION_SCRIPT_FILENAME When running ``alembic revision -m "rev1"``, we will now see the ``black`` @@ -798,13 +800,13 @@ configuration as follows:: # format using "black", then "zimports" hooks=black, zimports - black.type=console_scripts - black.entrypoint=black - black.options=-l 79 + black.type = console_scripts + black.entrypoint = black + black.options = -l 79 REVISION_SCRIPT_FILENAME - zimports.type=console_scripts - zimports.entrypoint=zimports - zimports.options=--style google + zimports.type = console_scripts + zimports.entrypoint = zimports + zimports.options = --style google REVISION_SCRIPT_FILENAME When using the above configuration, a newly generated revision file will be processed first by the "black" tool, then by the "zimports" tool. @@ -855,9 +857,9 @@ Our new ``"spaces_to_tabs"`` hook can be configured in alembic.ini as follows:: [post_write_hooks] - hooks=spaces_to_tabs + hooks = spaces_to_tabs - spaces_to_tabs.type=spaces_to_tabs + spaces_to_tabs.type = spaces_to_tabs When ``alembic revision`` is run, the ``env.py`` file will be loaded in all diff --git a/docs/build/tutorial.rst b/docs/build/tutorial.rst index 54da111..2ef8419 100644 --- a/docs/build/tutorial.rst +++ b/docs/build/tutorial.rst @@ -173,10 +173,10 @@ The file generated with the "generic" configuration looks like:: # format using "black" - use the console_scripts runner, # against the "black" entrypoint - # hooks=black - # black.type=console_scripts - # black.entrypoint=black - # black.options=-l 79 + # hooks = black + # black.type = console_scripts + # black.entrypoint = black + # black.options = -l 79 REVISION_SCRIPT_FILENAME # Logging configuration [loggers] diff --git a/docs/build/unreleased/819.rst b/docs/build/unreleased/819.rst new file mode 100644 index 0000000..583134a --- /dev/null +++ b/docs/build/unreleased/819.rst @@ -0,0 +1,9 @@ +.. change:: + :tags: bug, feature, documentation + :tickets: 819 + + Fix the documentation regarding the default command-line argument position of + the revision script filename within the post-write hook arguments. Implement a + ``REVISION_SCRIPT_FILENAME`` token, enabling the position to be changed. Switch + from ``str.split()`` to ``shlex.split()`` for more robust command-line argument + parsing. diff --git a/tests/test_post_write.py b/tests/test_post_write.py index f93ca90..653e09c 100644 --- a/tests/test_post_write.py +++ b/tests/test_post_write.py @@ -125,17 +125,10 @@ class RunHookTest(TestBase): message="x", ) - def test_console_scripts(self): - self.cfg = _no_sql_testing_config( - directives=( - "\n[post_write_hooks]\n" - "hooks=black\n" - "black.type=console_scripts\n" - "black.entrypoint=black\n" - "black.options=-l 79\n" - ) - ) - + def _run_black_with_config( + self, input_config, expected_additional_arguments_fn + ): + self.cfg = _no_sql_testing_config(directives=input_config) impl = mock.Mock(attrs=("foo", "bar"), module_name="black_module") entrypoints = mock.Mock(return_value=iter([impl])) with mock.patch( @@ -155,10 +148,41 @@ class RunHookTest(TestBase): sys.executable, "-c", "import black_module; black_module.foo.bar()", - rev.path, - "-l", - "79", ] + + expected_additional_arguments_fn(rev.path) ) ], ) + + def test_console_scripts(self): + input_config = """ +[post_write_hooks] +hooks = black +black.type = console_scripts +black.entrypoint = black +black.options = -l 79 + """ + + def expected_additional_arguments_fn(rev_path): + return [rev_path, "-l", "79"] + + self._run_black_with_config( + input_config, expected_additional_arguments_fn + ) + + def test_filename_interpolation(self): + input_config = """ +[post_write_hooks] +hooks = black +black.type = console_scripts +black.entrypoint = black +black.options = arg1 REVISION_SCRIPT_FILENAME 'multi-word arg' \ + --flag1='REVISION_SCRIPT_FILENAME' + """ + + def expected_additional_arguments_fn(rev_path): + return ["arg1", rev_path, "multi-word arg", "--flag1=" + rev_path] + + self._run_black_with_config( + input_config, expected_additional_arguments_fn + ) |