Add some docstrings to the base classes for management commands. Refs #9170.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@9082 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
James Bennett 2008-09-22 06:03:24 +00:00
parent 922aba3def
commit 5563362c4c
1 changed files with 186 additions and 7 deletions

View File

@ -1,3 +1,9 @@
"""
Base classes for writing management commands (named commands which can
be executed through ``django-admin.py`` or ``manage.py``).
"""
import os import os
import sys import sys
from optparse import make_option, OptionParser from optparse import make_option, OptionParser
@ -12,13 +18,26 @@ except NameError:
from sets import Set as set # For Python 2.3 from sets import Set as set # For Python 2.3
class CommandError(Exception): class CommandError(Exception):
"""
Exception class indicating a problem while executing a management
command.
If this exception is raised during the execution of a management
command, it will be caught and turned into a nicely-printed error
message to the appropriate output stream (i.e., stderr); as a
result, raising this exception (with a sensible description of the
error) is the preferred way to indicate that something has gone
wrong in the execution of a command.
"""
pass pass
def handle_default_options(options): def handle_default_options(options):
""" """
Include any default options that all commands should accept Include any default options that all commands should accept here
here so that ManagementUtility can handle them before searching so that ManagementUtility can handle them before searching for
for user commands. user commands.
""" """
if options.settings: if options.settings:
os.environ['DJANGO_SETTINGS_MODULE'] = options.settings os.environ['DJANGO_SETTINGS_MODULE'] = options.settings
@ -26,6 +45,80 @@ def handle_default_options(options):
sys.path.insert(0, options.pythonpath) sys.path.insert(0, options.pythonpath)
class BaseCommand(object): class BaseCommand(object):
"""
The base class from which all management commands ultimately
derive.
Use this class if you want access to all of the mechanisms which
parse the command-line arguments and work out what code to call in
response; if you don't need to change any of that behavior,
consider using one of the subclasses defined in this file.
If you are interested in overriding/customizing various aspects of
the command-parsing and -execution behavior, the normal flow works
as follows:
1. ``django-admin.py`` or ``manage.py`` loads the command class
and calls its ``run_from_argv()`` method.
2. The ``run_from_argv()`` method calls ``create_parser()`` to get
an ``OptionParser`` for the arguments, parses them, performs
any environment changes requested by options like
``pythonpath``, and then calls the ``execute()`` method,
passing the parsed arguments.
3. The ``execute()`` method attempts to carry out the command by
calling the ``handle()`` method with the parsed arguments; any
output produced by ``handle()`` will be printed to standard
output and, if the command is intended to produce a block of
SQL statements, will be wrapped in ``BEGIN`` and ``COMMIT``.
4. If ``handle()`` raised a ``ComandError``, ``execute()`` will
instead print an error message to ``stderr``.
Thus, the ``handle()`` method is typically the starting point for
subclasses; many built-in commands and command types either place
all of their logic in ``handle()``, or perform some additional
parsing work in ``handle()`` and then delegate from it to more
specialized methods as needed.
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 '<appname
appname ...>'.
``can_import_settings``
A boolean indicating whether the command needs to be able to
import Django settings; if ``True``, ``execute()`` will verify
that this is possible before proceeding. Default value is
``True``.
``help``
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.
``output_transaction``
A boolean indicating whether the command outputs SQL
statements; if ``True``, the output will automatically be
wrapped with ``BEGIN;`` and ``COMMIT;``. Default value is
``False``.
``requires_model_validation``
A boolean; if ``True``, validation of installed models will be
performed prior to executing the command. Default value is
``True``. To validate an individual application's models
rather than all applications' models, call
``self.validate(app)`` from ``handle()``, where ``app`` is the
application's Python module.
"""
# Metadata about this command. # Metadata about this command.
option_list = ( option_list = (
make_option('--settings', make_option('--settings',
@ -48,12 +141,19 @@ class BaseCommand(object):
def get_version(self): def get_version(self):
""" """
Returns the Django version, which should be correct for all built-in Return the Django version, which should be correct for all
Django commands. User-supplied commands should override this method. built-in Django commands. User-supplied commands should
override this method.
""" """
return django.get_version() return django.get_version()
def usage(self, subcommand): def usage(self, subcommand):
"""
Return a brief description of how to use this command, by
default from the attribute ``self.help``.
"""
usage = '%%prog %s [options] %s' % (subcommand, self.args) usage = '%%prog %s [options] %s' % (subcommand, self.args)
if self.help: if self.help:
return '%s\n\n%s' % (usage, self.help) return '%s\n\n%s' % (usage, self.help)
@ -61,22 +161,45 @@ class BaseCommand(object):
return usage return usage
def create_parser(self, prog_name, subcommand): def create_parser(self, prog_name, subcommand):
"""
Create and return the ``OptionParser`` which will be used to
parse the arguments to this command.
"""
return OptionParser(prog=prog_name, return OptionParser(prog=prog_name,
usage=self.usage(subcommand), usage=self.usage(subcommand),
version=self.get_version(), version=self.get_version(),
option_list=self.option_list) option_list=self.option_list)
def print_help(self, prog_name, subcommand): def print_help(self, prog_name, subcommand):
"""
Print the help message for this command, derived from
``self.usage()``.
"""
parser = self.create_parser(prog_name, subcommand) parser = self.create_parser(prog_name, subcommand)
parser.print_help() parser.print_help()
def run_from_argv(self, argv): def run_from_argv(self, argv):
"""
Set up any environment changes requested (e.g., Python path
and Django settings), then run this command.
"""
parser = self.create_parser(argv[0], argv[1]) parser = self.create_parser(argv[0], argv[1])
options, args = parser.parse_args(argv[2:]) options, args = parser.parse_args(argv[2:])
handle_default_options(options) handle_default_options(options)
self.execute(*args, **options.__dict__) self.execute(*args, **options.__dict__)
def execute(self, *args, **options): def execute(self, *args, **options):
"""
Try to execute this command, performing model validation if
needed (as controlled by the attribute
``self.requires_model_validation``). If the command raises a
``CommandError``, intercept it and print it sensibly to
stderr.
"""
# Switch to English, because django-admin.py creates database content # Switch to English, because django-admin.py creates database content
# like permissions, and those shouldn't contain any translations. # like permissions, and those shouldn't contain any translations.
# But only do this if we can assume we have a working settings file, # But only do this if we can assume we have a working settings file,
@ -112,6 +235,7 @@ class BaseCommand(object):
Validates the given app, raising CommandError for any errors. Validates the given app, raising CommandError for any errors.
If app is None, then this will validate all installed apps. If app is None, then this will validate all installed apps.
""" """
from django.core.management.validation import get_validation_errors from django.core.management.validation import get_validation_errors
try: try:
@ -128,9 +252,22 @@ class BaseCommand(object):
print "%s error%s found" % (num_errors, num_errors != 1 and 's' or '') print "%s error%s found" % (num_errors, num_errors != 1 and 's' or '')
def handle(self, *args, **options): def handle(self, *args, **options):
"""
The actual logic of the command. Subclasses must implement
this method.
"""
raise NotImplementedError() raise NotImplementedError()
class AppCommand(BaseCommand): class AppCommand(BaseCommand):
"""
A management command which takes one or more installed application
names as arguments, and does something with each of them.
Rather than implementing ``handle()``, subclasses must implement
``handle_app()``, which will be called once for each application.
"""
args = '<appname appname ...>' args = '<appname appname ...>'
def handle(self, *app_labels, **options): def handle(self, *app_labels, **options):
@ -149,9 +286,27 @@ class AppCommand(BaseCommand):
return '\n'.join(output) return '\n'.join(output)
def handle_app(self, app, **options): def handle_app(self, app, **options):
"""
Perform the command's actions for ``app``, which will be the
Python module corresponding to an application name given on
the command line.
"""
raise NotImplementedError() raise NotImplementedError()
class LabelCommand(BaseCommand): class LabelCommand(BaseCommand):
"""
A management command which takes one or more arbitrary arguments
(labels) on the command line, and does something with each of
them.
Rather than implementing ``handle()``, subclasses must implement
``handle_label()``, which will be called once for each label.
If the arguments should be names of installed applications, use
``AppCommand`` instead.
"""
args = '<label label ...>' args = '<label label ...>'
label = 'label' label = 'label'
@ -167,9 +322,24 @@ class LabelCommand(BaseCommand):
return '\n'.join(output) return '\n'.join(output)
def handle_label(self, label, **options): def handle_label(self, label, **options):
"""
Perform the command's actions for ``label``, which will be the
string as given on the command line.
"""
raise NotImplementedError() raise NotImplementedError()
class NoArgsCommand(BaseCommand): class NoArgsCommand(BaseCommand):
"""
A command which takes no arguments on the command line.
Rather than implementing ``handle()``, subclasses must implement
``handle_noargs()``; ``handle()`` itself is overridden to ensure
no arguments are passed to the command.
Attempting to pass arguments will raise ``CommandError``.
"""
args = '' args = ''
def handle(self, *args, **options): def handle(self, *args, **options):
@ -178,12 +348,17 @@ class NoArgsCommand(BaseCommand):
return self.handle_noargs(**options) return self.handle_noargs(**options)
def handle_noargs(self, **options): def handle_noargs(self, **options):
"""
Perform this command's actions.
"""
raise NotImplementedError() raise NotImplementedError()
def copy_helper(style, app_or_project, name, directory, other_name=''): def copy_helper(style, app_or_project, name, directory, other_name=''):
""" """
Copies either a Django application layout template or a Django project Copies either a Django application layout template or a Django project
layout template into the specified directory. layout template into the specified directory.
""" """
# style -- A color style object (see django.core.management.color). # style -- A color style object (see django.core.management.color).
# app_or_project -- The string 'app' or 'project'. # app_or_project -- The string 'app' or 'project'.
@ -236,7 +411,11 @@ def copy_helper(style, app_or_project, name, directory, other_name=''):
sys.stderr.write(style.NOTICE("Notice: Couldn't set permission bits on %s. You're probably using an uncommon filesystem setup. No problem.\n" % path_new)) sys.stderr.write(style.NOTICE("Notice: Couldn't set permission bits on %s. You're probably using an uncommon filesystem setup. No problem.\n" % path_new))
def _make_writeable(filename): def _make_writeable(filename):
"Makes sure that the file is writeable. Useful if our source is read-only." """
Make sure that the file is writeable. Useful if our source is
read-only.
"""
import stat import stat
if sys.platform.startswith('java'): if sys.platform.startswith('java'):
# On Jython there is no os.access() # On Jython there is no os.access()