Compatibility optparse argparse Django

Compatibility optparse argparse Django

Published Aug. 5, 2016 in Development, Tests and quality - Last update on Aug. 5, 2016.

Django 1.10 is here ! Above its new features there are some deleted code and I came up against optparse removing. To be honest, this module is deprecated since Python 2.7 (still here in 3.6), deprecated in Django since 1.7 and removed in 1.10.

Don't blame me too fast, I took the wave with everyone and stopped to use optparse in favor of argparse. But I maintain Django-DBBackup and this project must support all Django versions from 1.6.

What is the problem ?

Developers who used Django <= 1.7 will remember this kind of code:

from optparse import make_option

class Command(BaseCommand):
    """This is my old style command."""
    option_list = BaseCommand.option_list + (
        make_option("--noinput", action='store_false', dest='interactive', default=True,
                    help='Tells Django to NOT prompt the user for input of any kind.'),
        make_option('-q', "--quiet", action='store_true', default=False,
                    help='Tells Django to NOT output other text than errors.')
    )

This way was horrible because you must declare yourself Django common options making heritage complex.

The new style is the following:

class Command(BaseCommand):
    """This is my new style command."""
    def add_arguements(self, parser):
        parser.add_argument("--noinput", action='store_false', dest='interactive', default=True,
                            help='Tells Django to NOT prompt the user for input of any kind.'),
        parser.add_argument('-q', "--quiet", action='store_true', default=False,
                            help='Tells Django to NOT output other text than errors.')

A new add_arguments method has been added to mechanism allowing to handle the parser in every aspect.

Django lets users use both methods during 1.7 to 1.9, but with 1.10 Command.option_list became useless making new style mandatory. DBBackup must deal with 1.6 and 1.10, it must be able to use both style.

My solution

The solution has several requirements and pitfalls:

  • If Django < 1.10, we must set Command.option_list
  • if Django >= 1.10, we must use Command.add_arguments
  • We can't use only Command.add_arguments because it isn't in 1.6 mechanism
  • Some arguments are unsed in argparse
  • I want to make it as DRY as possible
from optparse import make_option as optparse_make_option
import django
from django.core.management.base import BaseCommand as _BaseCommand, CommandError

# Useful only in optparse
USELESS_ARGS = ('callback', 'callback_args', 'callback_kwargs', 'metavar')
# In optparse types are str
TYPES = {
    'string': str,
    'int': int,
    'long': long,
    'float': float,
    'complex': complex,
    'choice': list
}

# Dummy function for keep old code style
def make_option(*args, **kwargs):
    return args, kwargs


class BaseCommand(_BaseCommand):
    """Inherit from it for get command compatible Django 1.6-1.10"""
    # Options given to children classes
    base_option_list = (
        make_option("--noinput", action='store_false', dest='interactive', default=True,
                    help='Tells Django to NOT prompt the user for input of any kind.'),
        make_option('-q', "--quiet", action='store_true', default=False,
                    help='Tells Django to NOT output other text than errors.')
    )
    # Children options, unused here
    option_list = ()

    def __init__(self, *args, **kwargs):
        # Merge parent and parent options
        self.option_list = self.base_option_list + self.option_list
        if django.VERSION < (1, 10):
            # Create real optparse.Option
            options = tuple([optparse_make_option(*_args, **_kwargs)
                             for _args, _kwargs in self.option_list])
            self.option_list = options + _BaseCommand.option_list
        super(BaseCommand, self).__init__(*args, **kwargs)

    # Used only with 1.10
    def add_arguments(self, parser):
        for args, kwargs in self.option_list:
            kwargs = dict([
                (k, v) for k, v in kwargs.items()
                if not k.startswith('_') and
                k not in USELESS_ARGS])
            parser.add_argument(*args, **kwargs)

It is a BaseCommand, the final one can look like:

from ._base import BaseCommand, make_option


class Command(BaseCommand):
    option_list = (
        make_option("-x", "--xxx", help="XXX argument"),
)

Simply...

Explanation please ?

As you guess, I made a mix between both styles but instead of use make_option for create an optparse.Option, I simply store args & kwargs and use them with the appropriate way depending of Django version:

  • In Django < 1.10, stored args/kwargs are used in __init__ to create optparse.Options
  • In Django >= 1.10 stored args/kwargs are used in add_arguments to create argparse.Action

References

Comments

No comments yet.

Post your comment

Comment as . Log out.