diff --git a/django/core/management/__init__.py b/django/core/management/__init__.py index dec7c052b3..7e4982f0a2 100644 --- a/django/core/management/__init__.py +++ b/django/core/management/__init__.py @@ -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} - 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) + # 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 + } + 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', ()) 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] diff --git a/django/core/management/base.py b/django/core/management/base.py index 7eaea741e0..10d1b728fe 100644 --- a/django/core/management/base.py +++ b/django/core/management/base.py @@ -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 ''. - ``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,59 +236,26 @@ 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()) - 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) + 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.") + self.add_arguments(parser) return parser def add_arguments(self, 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) + 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', ()) handle_default_options(options) try: self.execute(*args, **cmd_options) diff --git a/django/core/management/commands/test.py b/django/core/management/commands/test.py index ad54994d74..6fdaca944c 100644 --- a/django/core/management/commands/test.py +++ b/django/core/management/commands/test.py @@ -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) diff --git a/docs/howto/custom-management-commands.txt b/docs/howto/custom-management-commands.txt index d5dd800a94..cc7653dd8e 100644 --- a/docs/howto/custom-management-commands.txt +++ b/docs/howto/custom-management-commands.txt @@ -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`. -.. 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 ''. - - .. 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 `. - .. attribute:: BaseCommand.output_transaction A boolean indicating whether the command outputs SQL statements; if diff --git a/docs/releases/1.8.txt b/docs/releases/1.8.txt index 1e0c233cba..e6b606505f 100644 --- a/docs/releases/1.8.txt +++ b/docs/releases/1.8.txt @@ -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 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/docs/topics/testing/advanced.txt b/docs/topics/testing/advanced.txt index eacc04c6d6..f2a2b103fc 100644 --- a/docs/topics/testing/advanced.txt +++ b/docs/topics/testing/advanced.txt @@ -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 ~~~~~~~ diff --git a/tests/user_commands/management/commands/optparse_cmd.py b/tests/user_commands/management/commands/optparse_cmd.py deleted file mode 100644 index 09de44b200..0000000000 --- a/tests/user_commands/management/commands/optparse_cmd.py +++ /dev/null @@ -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"]) diff --git a/tests/user_commands/tests.py b/tests/user_commands/tests.py index a50e714c94..8bceccc487 100644 --- a/tests/user_commands/tests.py +++ b/tests/user_commands/tests.py @@ -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)