From cbff097bd91fad42c7231026968f686598b1d7a2 Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Fri, 6 Jun 2014 17:55:56 +0200 Subject: [PATCH] Documented optparse to argparse changes for management commands --- docs/howto/custom-management-commands.txt | 79 +++++++++++++++++++---- docs/internals/deprecation.txt | 3 + docs/releases/1.8.txt | 25 +++++++ 3 files changed, 93 insertions(+), 14 deletions(-) diff --git a/docs/howto/custom-management-commands.txt b/docs/howto/custom-management-commands.txt index bbbd31af6c..bb15597a34 100644 --- a/docs/howto/custom-management-commands.txt +++ b/docs/howto/custom-management-commands.txt @@ -50,13 +50,15 @@ look like this: from polls.models import Poll class Command(BaseCommand): - args = '' help = 'Closes the specified poll for voting' + def add_arguments(self, parser): + parser.add_argument('poll_id', nargs='+', type=int) + def handle(self, *args, **options): - for poll_id in args: + for poll_id in options['poll_id']: try: - poll = Poll.objects.get(pk=int(poll_id)) + poll = Poll.objects.get(pk=poll_id) except Poll.DoesNotExist: raise CommandError('Poll "%s" does not exist' % poll_id) @@ -65,6 +67,14 @@ look like this: self.stdout.write('Successfully closed poll "%s"' % poll_id) +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:: @@ -81,28 +91,34 @@ look like this: The new custom command can be called using ``python manage.py closepoll ``. -The ``handle()`` method takes zero or more ``poll_ids`` and sets ``poll.opened`` +The ``handle()`` method takes one or more ``poll_ids`` and sets ``poll.opened`` to ``False`` for each one. If the user referenced any nonexistent polls, a :class:`CommandError` is raised. The ``poll.opened`` attribute does not exist in the :doc:`tutorial` and was added to ``polls.models.Poll`` for this example. +.. _custom-commands-options: + +Accepting optional arguments +============================ + The same ``closepoll`` could be easily modified to delete a given poll instead -of closing it by accepting additional command line options. These custom options -must be added to :attr:`~BaseCommand.option_list` like this: +of closing it by accepting additional command line options. These custom +options can be added in the :meth:`~BaseCommand.add_arguments` method like this: .. code-block:: python - from optparse import make_option - class Command(BaseCommand): - option_list = BaseCommand.option_list + ( - make_option('--delete', + def add_arguments(self, parser): + # Positional arguments + parser.add_argument('poll_id', nargs='+', type=int) + + # Named (optional) arguments + parser.add_argument('--delete', action='store_true', dest='delete', default=False, - help='Delete poll instead of closing it'), - ) + help='Delete poll instead of closing it') def handle(self, *args, **options): # ... @@ -110,9 +126,15 @@ must be added to :attr:`~BaseCommand.option_list` 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:`optparse` Python documentation -for more about ``make_option`` usage. +parameter of the handle method. See the :py:mod:`argparse` Python documentation +for more about ``add_argument`` usage. In addition to being able to add custom command line options, all :doc:`management commands` can accept some @@ -202,6 +224,12 @@ All attributes can be set in your derived class and can be used in 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 @@ -215,11 +243,25 @@ All attributes can be set in your derived class and can be used in help message when the user runs the command ``python manage.py help ``. +.. attribute:: BaseCommand.missing_args_message + +.. versionadded:: 1.8 + + If your command defines mandatory positional arguments, you can customize + 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 @@ -287,6 +329,15 @@ the :meth:`~BaseCommand.handle` method must be implemented. super(Command, self).__init__(*args, **kwargs) # ... +.. method:: BaseCommand.add_arguments(parser) + +.. versionadded:: 1.8 + + Entry point to add parser arguments to handle command line arguments passed + to the command. Custom commands should override this method to add both + positional and optional arguments accepted by the command. Calling + ``super()`` is not needed when directly subclassing ``BaseCommand``. + .. method:: BaseCommand.get_version() Return the Django version, which should be correct for all diff --git a/docs/internals/deprecation.txt b/docs/internals/deprecation.txt index 3cbd1e3173..82ffe4938f 100644 --- a/docs/internals/deprecation.txt +++ b/docs/internals/deprecation.txt @@ -35,6 +35,9 @@ about each item can often be found in the release notes of two versions prior. * The ability to :func:`~django.core.urlresolvers.reverse` URLs using a dotted Python path will be removed. +* Support for :py:mod:`optparse` will be dropped for custom management commands + (replaced by :py:mod:`argparse`). + .. _deprecation-removed-in-1.9: 1.9 diff --git a/docs/releases/1.8.txt b/docs/releases/1.8.txt index f4098fa08a..437c27189f 100644 --- a/docs/releases/1.8.txt +++ b/docs/releases/1.8.txt @@ -280,6 +280,20 @@ Now, an error will be raised to prevent data loss:: ... ValueError: Cannot assign "": "Author" instance isn't saved in the database. +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. 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`. + Miscellaneous ~~~~~~~~~~~~~ @@ -409,3 +423,14 @@ Similarly for GIS sitemaps, add ``name='django.contrib.gis.sitemaps.views.kml'`` or ``name='django.contrib.gis.sitemaps.views.kmz'``. .. _security issue: https://www.djangoproject.com/weblog/2014/apr/21/security/#s-issue-unexpected-code-execution-using-reverse + +Extending management command arguments through ``Command.option_list`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Management commands now use :py:mod:`argparse` instead of :py:mod:`optparse` to +parse command-line arguments passed to commands. This also means that the way +to add custom arguments to commands has changed: instead of extending the +``option_list`` class list, you should now override the +:meth:`~django.core.management.BaseCommand.add_arguments` method and add +arguments through ``argparse.add_argument()``. See +:ref:`this example ` for more details.