Refs #19973 -- Removed optparse support in management commands per deprecation timeline.

This commit is contained in:
Tim Graham 2015-08-18 12:46:03 -04:00
parent 3bbebd06ad
commit 6a70cb5397
8 changed files with 47 additions and 209 deletions

View File

@ -100,19 +100,16 @@ def call_command(name, *args, **options):
# Simulate argument parsing to get the option defaults (see #10080 for details).
parser = command.create_parser('', name)
if command.use_argparse:
# Use the `dest` option name from the parser option
opt_mapping = {sorted(s_opt.option_strings)[0].lstrip('-').replace('-', '_'): s_opt.dest
for s_opt in parser._actions if s_opt.option_strings}
opt_mapping = {
sorted(s_opt.option_strings)[0].lstrip('-').replace('-', '_'): s_opt.dest
for s_opt in parser._actions if s_opt.option_strings
}
arg_options = {opt_mapping.get(key, key): value for key, value in options.items()}
defaults = parser.parse_args(args=args)
defaults = dict(defaults._get_kwargs(), **arg_options)
# Move positional args out of options to mimic legacy optparse
args = defaults.pop('args', ())
else:
# Legacy optparse method
defaults, _ = parser.parse_args(args=[])
defaults = dict(defaults.__dict__, **options)
if 'skip_checks' not in options:
defaults['skip_checks'] = True
@ -249,12 +246,10 @@ class ManagementUtility(object):
# user will find out once they execute the command.
pass
parser = subcommand_cls.create_parser('', cwords[0])
if subcommand_cls.use_argparse:
options.extend((sorted(s_opt.option_strings)[0], s_opt.nargs != 0) for s_opt in
parser._actions if s_opt.option_strings)
else:
options.extend((s_opt.get_opt_string(), s_opt.nargs != 0) for s_opt in
parser.option_list)
options.extend(
(sorted(s_opt.option_strings)[0], s_opt.nargs != 0)
for s_opt in parser._actions if s_opt.option_strings
)
# filter out previously specified options from available options
prev_opts = [x.split('=')[0] for x in cwords[1:cword - 1]]
options = [opt for opt in options if opt[0] not in prev_opts]

View File

@ -9,7 +9,6 @@ import os
import sys
import warnings
from argparse import ArgumentParser
from optparse import OptionParser
import django
from django.core import checks
@ -152,12 +151,6 @@ class BaseCommand(object):
Several attributes affect behavior at various steps along the way:
``args``
A string listing the arguments accepted by the command,
suitable for use in help messages; e.g., a command which takes
a list of application names might set this to '<app_label
app_label ...>'.
``can_import_settings``
A boolean indicating whether the command needs to be able to
import Django settings; if ``True``, ``execute()`` will verify
@ -168,12 +161,6 @@ class BaseCommand(object):
A short description of the command, which will be printed in
help messages.
``option_list``
This is the list of ``optparse`` options which will be fed
into the command's ``OptionParser`` for parsing arguments.
Deprecated and will be removed in Django 1.10.
Use ``add_arguments`` instead.
``output_transaction``
A boolean indicating whether the command outputs SQL
statements; if ``True``, the output will automatically be
@ -207,9 +194,7 @@ class BaseCommand(object):
to settings. This condition will generate a CommandError.
"""
# Metadata about this command.
option_list = ()
help = ''
args = ''
# Configuration shortcuts that alter various logic.
_called_from_command_line = False
@ -227,10 +212,6 @@ class BaseCommand(object):
self.style = color_style()
self.stderr.style_func = self.style.ERROR
@property
def use_argparse(self):
return not bool(self.option_list)
def get_version(self):
"""
Return the Django version, which should be correct for all
@ -255,36 +236,6 @@ class BaseCommand(object):
Create and return the ``ArgumentParser`` which will be used to
parse the arguments to this command.
"""
if not self.use_argparse:
def store_as_int(option, opt_str, value, parser):
setattr(parser.values, option.dest, int(value))
# Backwards compatibility: use deprecated optparse module
warnings.warn("OptionParser usage for Django management commands "
"is deprecated, use ArgumentParser instead",
RemovedInDjango110Warning)
parser = OptionParser(prog=prog_name,
usage=self.usage(subcommand),
version=self.get_version())
parser.add_option('-v', '--verbosity', action='callback', dest='verbosity', default=1,
type='choice', choices=['0', '1', '2', '3'], callback=store_as_int,
help='Verbosity level; 0=minimal output, 1=normal output, 2=verbose output, 3=very verbose output')
parser.add_option('--settings',
help=(
'The Python path to a settings module, e.g. '
'"myproject.settings.main". If this isn\'t provided, the '
'DJANGO_SETTINGS_MODULE environment variable will be used.'
),
)
parser.add_option('--pythonpath',
help='A directory to add to the Python path, e.g. "/home/djangoprojects/myproject".'),
parser.add_option('--traceback', action='store_true',
help='Raise on CommandError exceptions')
parser.add_option('--no-color', action='store_true', dest='no_color', default=False,
help="Don't colorize the command output.")
for opt in self.option_list:
parser.add_option(opt)
else:
parser = CommandParser(self, prog="%s %s" % (os.path.basename(prog_name), subcommand),
description=self.help or None)
parser.add_argument('--version', action='version', version=self.get_version())
@ -304,9 +255,6 @@ class BaseCommand(object):
help='Raise on CommandError exceptions')
parser.add_argument('--no-color', action='store_true', dest='no_color', default=False,
help="Don't colorize the command output.")
if self.args:
# Keep compatibility and always accept positional arguments, like optparse when args is set
parser.add_argument('args', nargs='*')
self.add_arguments(parser)
return parser
@ -335,14 +283,10 @@ class BaseCommand(object):
self._called_from_command_line = True
parser = self.create_parser(argv[0], argv[1])
if self.use_argparse:
options = parser.parse_args(argv[2:])
cmd_options = vars(options)
# Move positional args out of options to mimic legacy optparse
args = cmd_options.pop('args', ())
else:
options, args = parser.parse_args(argv[2:])
cmd_options = vars(options)
handle_default_options(options)
try:
self.execute(*args, **cmd_options)

View File

@ -50,13 +50,6 @@ class Command(BaseCommand):
'default value is localhost:8081-8179.'),
test_runner_class = get_runner(settings, self.test_runner)
if hasattr(test_runner_class, 'option_list'):
# Keeping compatibility with both optparse and argparse at this level
# would be too heavy for a non-critical item
raise RuntimeError(
"The method to extend accepted command-line arguments by the "
"test management command has changed in Django 1.8. Please "
"create an add_arguments class method to achieve this.")
if hasattr(test_runner_class, 'add_arguments'):
test_runner_class.add_arguments(parser)

View File

@ -69,16 +69,6 @@ look like this::
self.stdout.write('Successfully closed poll "%s"' % poll_id)
.. versionchanged:: 1.8
Before Django 1.8, management commands were based on the :py:mod:`optparse`
module, and positional arguments were passed in ``*args`` while optional
arguments were passed in ``**options``. Now that management commands use
:py:mod:`argparse` for argument parsing, all arguments are passed in
``**options`` by default, unless you name your positional arguments to
``args`` (compatibility mode). You are encouraged to exclusively use
``**options`` for new commands.
.. _management-commands-output:
.. note::
@ -128,12 +118,6 @@ options can be added in the :meth:`~BaseCommand.add_arguments` method like this:
poll.delete()
# ...
.. versionchanged:: 1.8
Previously, only the standard :py:mod:`optparse` library was supported and
you would have to extend the command ``option_list`` variable with
``optparse.make_option()``.
The option (``delete`` in our example) is available in the options dict
parameter of the handle method. See the :py:mod:`argparse` Python documentation
for more about ``add_argument`` usage.
@ -227,19 +211,6 @@ Attributes
All attributes can be set in your derived class and can be used in
:class:`BaseCommand`s :ref:`subclasses<ref-basecommand-subclasses>`.
.. attribute:: BaseCommand.args
A string listing the arguments accepted by the command,
suitable for use in help messages; e.g., a command which takes
a list of application names might set this to '<app_label
app_label ...>'.
.. deprecated:: 1.8
This should be done now in the :meth:`~BaseCommand.add_arguments()`
method, by calling the ``parser.add_argument()`` method. See the
``closepoll`` example above.
.. attribute:: BaseCommand.can_import_settings
A boolean indicating whether the command needs to be able to
@ -261,17 +232,6 @@ All attributes can be set in your derived class and can be used in
the message error returned in the case of missing arguments. The default is
output by :py:mod:`argparse` ("too few arguments").
.. attribute:: BaseCommand.option_list
This is the list of ``optparse`` options which will be fed
into the command's ``OptionParser`` for parsing arguments.
.. deprecated:: 1.8
You should now override the :meth:`~BaseCommand.add_arguments` method
to add custom arguments accepted by your command. See :ref:`the example
above <custom-commands-options>`.
.. attribute:: BaseCommand.output_transaction
A boolean indicating whether the command outputs SQL statements; if

View File

@ -738,15 +738,14 @@ Management commands that only accept positional arguments
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If you have written a custom management command that only accepts positional
arguments and you didn't specify the
:attr:`~django.core.management.BaseCommand.args` command variable, you might
get an error like ``Error: unrecognized arguments: ...``, as variable parsing
is now based on :py:mod:`argparse` which doesn't implicitly accept positional
arguments and you didn't specify the ``args`` command variable, you might get
an error like ``Error: unrecognized arguments: ...``, as variable parsing is
now based on :py:mod:`argparse` which doesn't implicitly accept positional
arguments. You can make your command backwards compatible by simply setting the
:attr:`~django.core.management.BaseCommand.args` class variable. However, if
you don't have to keep compatibility with older Django versions, it's better to
implement the new :meth:`~django.core.management.BaseCommand.add_arguments`
method as described in :doc:`/howto/custom-management-commands`.
``args`` class variable. However, if you don't have to keep compatibility with
older Django versions, it's better to implement the new
:meth:`~django.core.management.BaseCommand.add_arguments` method as described
in :doc:`/howto/custom-management-commands`.
Custom test management command arguments through test runner
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -417,10 +417,6 @@ execute and tear down the test suite.
.. versionchanged:: 1.8
Previously, you had to provide an ``option_list`` attribute to a
subclassed test runner to add options to the list of command-line
options that the :djadmin:`test` command could use.
The ``keepdb``, ``reverse``, and ``debug_sql`` arguments were added.
Attributes
@ -448,18 +444,6 @@ Attributes
By default it is set to ``unittest.defaultTestLoader``. You can override
this attribute if your tests are going to be loaded in unusual ways.
.. attribute:: DiscoverRunner.option_list
This is the tuple of ``optparse`` options which will be fed into the
management command's ``OptionParser`` for parsing arguments. See the
documentation for Python's ``optparse`` module for more details.
.. deprecated:: 1.8
You should now override the :meth:`~DiscoverRunner.add_arguments` class
method to add custom arguments accepted by the :djadmin:`test`
management command.
Methods
~~~~~~~

View File

@ -1,22 +0,0 @@
from optparse import make_option
from django.core.management.base import BaseCommand
class Command(BaseCommand):
help = "Test optparse compatibility."
args = ''
option_list = BaseCommand.option_list + (
make_option("-s", "--style", default="Rock'n'Roll"),
make_option("-x", "--example")
)
def handle(self, *args, **options):
options["example"]
# BaseCommand default option is available
options['verbosity']
assert (
isinstance(options['verbosity'], int), "verbosity option is not int, but %s" % type(options['verbosity'])
)
self.stdout.write("All right, let's dance %s." % options["style"])

View File

@ -5,11 +5,10 @@ from django.core import management
from django.core.management import BaseCommand, CommandError, find_commands
from django.core.management.utils import find_command, popen_wrapper
from django.db import connection
from django.test import SimpleTestCase, ignore_warnings, override_settings
from django.test.utils import captured_stderr, captured_stdout, extend_sys_path
from django.test import SimpleTestCase, override_settings
from django.test.utils import captured_stderr, extend_sys_path
from django.utils import translation
from django.utils._os import upath
from django.utils.deprecation import RemovedInDjango110Warning
from django.utils.six import StringIO
@ -104,20 +103,6 @@ class CommandTests(SimpleTestCase):
self.assertNotIn("opt_3", out.getvalue())
self.assertNotIn("opt-3", out.getvalue())
@ignore_warnings(category=RemovedInDjango110Warning)
def test_optparse_compatibility(self):
"""
optparse should be supported during Django 1.8/1.9 releases.
"""
out = StringIO()
management.call_command('optparse_cmd', stdout=out)
self.assertEqual(out.getvalue(), "All right, let's dance Rock'n'Roll.\n")
# Simulate command line execution
with captured_stdout() as stdout, captured_stderr():
management.execute_from_command_line(['django-admin', 'optparse_cmd'])
self.assertEqual(stdout.getvalue(), "All right, let's dance Rock'n'Roll.\n")
def test_calling_a_command_with_only_empty_parameter_should_ends_gracefully(self):
out = StringIO()
management.call_command('hal', "--empty", stdout=out)