Kaydet (Commit) 6a70cb53 authored tarafından Tim Graham's avatar Tim Graham

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

üst 3bbebd06
...@@ -100,19 +100,16 @@ def call_command(name, *args, **options): ...@@ -100,19 +100,16 @@ def call_command(name, *args, **options):
# Simulate argument parsing to get the option defaults (see #10080 for details). # Simulate argument parsing to get the option defaults (see #10080 for details).
parser = command.create_parser('', name) parser = command.create_parser('', name)
if command.use_argparse: # Use the `dest` option name from the parser option
# Use the `dest` option name from the parser option opt_mapping = {
opt_mapping = {sorted(s_opt.option_strings)[0].lstrip('-').replace('-', '_'): s_opt.dest sorted(s_opt.option_strings)[0].lstrip('-').replace('-', '_'): s_opt.dest
for s_opt in parser._actions if s_opt.option_strings} 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) arg_options = {opt_mapping.get(key, key): value for key, value in options.items()}
defaults = dict(defaults._get_kwargs(), **arg_options) defaults = parser.parse_args(args=args)
# Move positional args out of options to mimic legacy optparse defaults = dict(defaults._get_kwargs(), **arg_options)
args = defaults.pop('args', ()) # Move positional args out of options to mimic legacy optparse
else: args = defaults.pop('args', ())
# Legacy optparse method
defaults, _ = parser.parse_args(args=[])
defaults = dict(defaults.__dict__, **options)
if 'skip_checks' not in options: if 'skip_checks' not in options:
defaults['skip_checks'] = True defaults['skip_checks'] = True
...@@ -249,12 +246,10 @@ class ManagementUtility(object): ...@@ -249,12 +246,10 @@ class ManagementUtility(object):
# user will find out once they execute the command. # user will find out once they execute the command.
pass pass
parser = subcommand_cls.create_parser('', cwords[0]) parser = subcommand_cls.create_parser('', cwords[0])
if subcommand_cls.use_argparse: options.extend(
options.extend((sorted(s_opt.option_strings)[0], s_opt.nargs != 0) for s_opt in (sorted(s_opt.option_strings)[0], s_opt.nargs != 0)
parser._actions if s_opt.option_strings) 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)
# filter out previously specified options from available options # filter out previously specified options from available options
prev_opts = [x.split('=')[0] for x in cwords[1:cword - 1]] 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] options = [opt for opt in options if opt[0] not in prev_opts]
......
...@@ -9,7 +9,6 @@ import os ...@@ -9,7 +9,6 @@ import os
import sys import sys
import warnings import warnings
from argparse import ArgumentParser from argparse import ArgumentParser
from optparse import OptionParser
import django import django
from django.core import checks from django.core import checks
...@@ -152,12 +151,6 @@ class BaseCommand(object): ...@@ -152,12 +151,6 @@ class BaseCommand(object):
Several attributes affect behavior at various steps along the way: 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`` ``can_import_settings``
A boolean indicating whether the command needs to be able to A boolean indicating whether the command needs to be able to
import Django settings; if ``True``, ``execute()`` will verify import Django settings; if ``True``, ``execute()`` will verify
...@@ -168,12 +161,6 @@ class BaseCommand(object): ...@@ -168,12 +161,6 @@ class BaseCommand(object):
A short description of the command, which will be printed in A short description of the command, which will be printed in
help messages. 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`` ``output_transaction``
A boolean indicating whether the command outputs SQL A boolean indicating whether the command outputs SQL
statements; if ``True``, the output will automatically be statements; if ``True``, the output will automatically be
...@@ -207,9 +194,7 @@ class BaseCommand(object): ...@@ -207,9 +194,7 @@ class BaseCommand(object):
to settings. This condition will generate a CommandError. to settings. This condition will generate a CommandError.
""" """
# Metadata about this command. # Metadata about this command.
option_list = ()
help = '' help = ''
args = ''
# Configuration shortcuts that alter various logic. # Configuration shortcuts that alter various logic.
_called_from_command_line = False _called_from_command_line = False
...@@ -227,10 +212,6 @@ class BaseCommand(object): ...@@ -227,10 +212,6 @@ class BaseCommand(object):
self.style = color_style() self.style = color_style()
self.stderr.style_func = self.style.ERROR self.stderr.style_func = self.style.ERROR
@property
def use_argparse(self):
return not bool(self.option_list)
def get_version(self): def get_version(self):
""" """
Return the Django version, which should be correct for all Return the Django version, which should be correct for all
...@@ -255,59 +236,26 @@ class BaseCommand(object): ...@@ -255,59 +236,26 @@ class BaseCommand(object):
Create and return the ``ArgumentParser`` which will be used to Create and return the ``ArgumentParser`` which will be used to
parse the arguments to this command. parse the arguments to this command.
""" """
if not self.use_argparse: parser = CommandParser(self, prog="%s %s" % (os.path.basename(prog_name), subcommand),
def store_as_int(option, opt_str, value, parser): description=self.help or None)
setattr(parser.values, option.dest, int(value)) parser.add_argument('--version', action='version', version=self.get_version())
parser.add_argument('-v', '--verbosity', action='store', dest='verbosity', default='1',
# Backwards compatibility: use deprecated optparse module type=int, choices=[0, 1, 2, 3],
warnings.warn("OptionParser usage for Django management commands " help='Verbosity level; 0=minimal output, 1=normal output, 2=verbose output, 3=very verbose output')
"is deprecated, use ArgumentParser instead", parser.add_argument('--settings',
RemovedInDjango110Warning) help=(
parser = OptionParser(prog=prog_name, 'The Python path to a settings module, e.g. '
usage=self.usage(subcommand), '"myproject.settings.main". If this isn\'t provided, the '
version=self.get_version()) 'DJANGO_SETTINGS_MODULE environment variable will be used.'
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_argument('--pythonpath',
parser.add_option('--settings', help='A directory to add to the Python path, e.g. "/home/djangoprojects/myproject".')
help=( parser.add_argument('--traceback', action='store_true',
'The Python path to a settings module, e.g. ' help='Raise on CommandError exceptions')
'"myproject.settings.main". If this isn\'t provided, the ' parser.add_argument('--no-color', action='store_true', dest='no_color', default=False,
'DJANGO_SETTINGS_MODULE environment variable will be used.' help="Don't colorize the command output.")
), self.add_arguments(parser)
)
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())
parser.add_argument('-v', '--verbosity', action='store', dest='verbosity', default='1',
type=int, choices=[0, 1, 2, 3],
help='Verbosity level; 0=minimal output, 1=normal output, 2=verbose output, 3=very verbose output')
parser.add_argument('--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_argument('--pythonpath',
help='A directory to add to the Python path, e.g. "/home/djangoprojects/myproject".')
parser.add_argument('--traceback', action='store_true',
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 return parser
def add_arguments(self, parser): def add_arguments(self, parser):
...@@ -335,14 +283,10 @@ class BaseCommand(object): ...@@ -335,14 +283,10 @@ class BaseCommand(object):
self._called_from_command_line = True self._called_from_command_line = True
parser = self.create_parser(argv[0], argv[1]) parser = self.create_parser(argv[0], argv[1])
if self.use_argparse: options = parser.parse_args(argv[2:])
options = parser.parse_args(argv[2:]) cmd_options = vars(options)
cmd_options = vars(options) # Move positional args out of options to mimic legacy optparse
# Move positional args out of options to mimic legacy optparse args = cmd_options.pop('args', ())
args = cmd_options.pop('args', ())
else:
options, args = parser.parse_args(argv[2:])
cmd_options = vars(options)
handle_default_options(options) handle_default_options(options)
try: try:
self.execute(*args, **cmd_options) self.execute(*args, **cmd_options)
......
...@@ -50,13 +50,6 @@ class Command(BaseCommand): ...@@ -50,13 +50,6 @@ class Command(BaseCommand):
'default value is localhost:8081-8179.'), 'default value is localhost:8081-8179.'),
test_runner_class = get_runner(settings, self.test_runner) 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'): if hasattr(test_runner_class, 'add_arguments'):
test_runner_class.add_arguments(parser) test_runner_class.add_arguments(parser)
......
...@@ -69,16 +69,6 @@ look like this:: ...@@ -69,16 +69,6 @@ look like this::
self.stdout.write('Successfully closed poll "%s"' % poll_id) 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: .. _management-commands-output:
.. note:: .. note::
...@@ -128,12 +118,6 @@ options can be added in the :meth:`~BaseCommand.add_arguments` method like this: ...@@ -128,12 +118,6 @@ options can be added in the :meth:`~BaseCommand.add_arguments` method like this:
poll.delete() 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 The option (``delete`` in our example) is available in the options dict
parameter of the handle method. See the :py:mod:`argparse` Python documentation parameter of the handle method. See the :py:mod:`argparse` Python documentation
for more about ``add_argument`` usage. for more about ``add_argument`` usage.
...@@ -227,19 +211,6 @@ Attributes ...@@ -227,19 +211,6 @@ Attributes
All attributes can be set in your derived class and can be used in All attributes can be set in your derived class and can be used in
:class:`BaseCommand`’s :ref:`subclasses<ref-basecommand-subclasses>`. :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 .. attribute:: BaseCommand.can_import_settings
A boolean indicating whether the command needs to be able to 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 ...@@ -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 the message error returned in the case of missing arguments. The default is
output by :py:mod:`argparse` ("too few arguments"). 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 .. attribute:: BaseCommand.output_transaction
A boolean indicating whether the command outputs SQL statements; if A boolean indicating whether the command outputs SQL statements; if
......
...@@ -738,15 +738,14 @@ Management commands that only accept positional arguments ...@@ -738,15 +738,14 @@ Management commands that only accept positional arguments
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If you have written a custom management command that only accepts positional If you have written a custom management command that only accepts positional
arguments and you didn't specify the arguments and you didn't specify the ``args`` command variable, you might get
:attr:`~django.core.management.BaseCommand.args` command variable, you might an error like ``Error: unrecognized arguments: ...``, as variable parsing is
get an error like ``Error: unrecognized arguments: ...``, as variable parsing now based on :py:mod:`argparse` which doesn't implicitly accept positional
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 arguments. You can make your command backwards compatible by simply setting the
:attr:`~django.core.management.BaseCommand.args` class variable. However, if ``args`` class variable. However, if you don't have to keep compatibility with
you don't have to keep compatibility with older Django versions, it's better to older Django versions, it's better to implement the new
implement the new :meth:`~django.core.management.BaseCommand.add_arguments` :meth:`~django.core.management.BaseCommand.add_arguments` method as described
method as described in :doc:`/howto/custom-management-commands`. in :doc:`/howto/custom-management-commands`.
Custom test management command arguments through test runner Custom test management command arguments through test runner
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
......
...@@ -417,10 +417,6 @@ execute and tear down the test suite. ...@@ -417,10 +417,6 @@ execute and tear down the test suite.
.. versionchanged:: 1.8 .. 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. The ``keepdb``, ``reverse``, and ``debug_sql`` arguments were added.
Attributes Attributes
...@@ -448,18 +444,6 @@ Attributes ...@@ -448,18 +444,6 @@ Attributes
By default it is set to ``unittest.defaultTestLoader``. You can override By default it is set to ``unittest.defaultTestLoader``. You can override
this attribute if your tests are going to be loaded in unusual ways. 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 Methods
~~~~~~~ ~~~~~~~
......
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"])
...@@ -5,11 +5,10 @@ from django.core import management ...@@ -5,11 +5,10 @@ from django.core import management
from django.core.management import BaseCommand, CommandError, find_commands from django.core.management import BaseCommand, CommandError, find_commands
from django.core.management.utils import find_command, popen_wrapper from django.core.management.utils import find_command, popen_wrapper
from django.db import connection from django.db import connection
from django.test import SimpleTestCase, ignore_warnings, override_settings from django.test import SimpleTestCase, override_settings
from django.test.utils import captured_stderr, captured_stdout, extend_sys_path from django.test.utils import captured_stderr, extend_sys_path
from django.utils import translation from django.utils import translation
from django.utils._os import upath from django.utils._os import upath
from django.utils.deprecation import RemovedInDjango110Warning
from django.utils.six import StringIO from django.utils.six import StringIO
...@@ -104,20 +103,6 @@ class CommandTests(SimpleTestCase): ...@@ -104,20 +103,6 @@ class CommandTests(SimpleTestCase):
self.assertNotIn("opt_3", out.getvalue()) self.assertNotIn("opt_3", out.getvalue())
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): def test_calling_a_command_with_only_empty_parameter_should_ends_gracefully(self):
out = StringIO() out = StringIO()
management.call_command('hal', "--empty", stdout=out) management.call_command('hal', "--empty", stdout=out)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment