1import functools
2import os
3import pkgutil
4import sys
5from argparse import (
6 _AppendConstAction,
7 _CountAction,
8 _StoreConstAction,
9 _SubParsersAction,
10)
11from collections import defaultdict
12from difflib import get_close_matches
13from importlib import import_module
14
15import django
16from django.apps import apps
17from django.conf import settings
18from django.core.exceptions import ImproperlyConfigured
19from django.core.management.base import (
20 BaseCommand,
21 CommandError,
22 CommandParser,
23 handle_default_options,
24)
25from django.core.management.color import color_style
26from django.utils import autoreload
27
28
29def find_commands(management_dir):
30 """
31 Given a path to a management directory, return a list of all the command
32 names that are available.
33 """
34 command_dir = os.path.join(management_dir, "commands")
35 return [
36 name
37 for _, name, is_pkg in pkgutil.iter_modules([command_dir])
38 if not is_pkg and not name.startswith("_")
39 ]
40
41
42def load_command_class(app_name, name):
43 """
44 Given a command name and an application name, return the Command
45 class instance. Allow all errors raised by the import process
46 (ImportError, AttributeError) to propagate.
47 """
48 module = import_module("%s.management.commands.%s" % (app_name, name))
49 return module.Command()
50
51
52@functools.cache
53def get_commands():
54 """
55 Return a dictionary mapping command names to their callback applications.
56
57 Look for a management.commands package in django.core, and in each
58 installed application -- if a commands package exists, register all
59 commands in that package.
60
61 Core commands are always included. If a settings module has been
62 specified, also include user-defined commands.
63
64 The dictionary is in the format {command_name: app_name}. Key-value
65 pairs from this dictionary can then be used in calls to
66 load_command_class(app_name, command_name)
67
68 The dictionary is cached on the first call and reused on subsequent
69 calls.
70 """
71 commands = {name: "django.core" for name in find_commands(__path__[0])}
72
73 if not settings.configured:
74 return commands
75
76 for app_config in reversed(apps.get_app_configs()):
77 path = os.path.join(app_config.path, "management")
78 commands.update({name: app_config.name for name in find_commands(path)})
79
80 return commands
81
82
83def call_command(command_name, *args, **options):
84 """
85 Call the given command, with the given options and args/kwargs.
86
87 This is the primary API you should use for calling specific commands.
88
89 `command_name` may be a string or a command object. Using a string is
90 preferred unless the command object is required for further processing or
91 testing.
92
93 Some examples:
94 call_command('migrate')
95 call_command('shell', plain=True)
96 call_command('sqlmigrate', 'myapp')
97
98 from django.core.management.commands import flush
99 cmd = flush.Command()
100 call_command(cmd, verbosity=0, interactive=False)
101 # Do something with cmd ...
102 """
103 if isinstance(command_name, BaseCommand):
104 # Command object passed in.
105 command = command_name
106 command_name = command.__class__.__module__.split(".")[-1]
107 else:
108 # Load the command object by name.
109 try:
110 app_name = get_commands()[command_name]
111 except KeyError:
112 raise CommandError("Unknown command: %r" % command_name)
113
114 if isinstance(app_name, BaseCommand):
115 # If the command is already loaded, use it directly.
116 command = app_name
117 else:
118 command = load_command_class(app_name, command_name)
119
120 # Simulate argument parsing to get the option defaults (see #10080 for details).
121 parser = command.create_parser("", command_name)
122 # Use the `dest` option name from the parser option
123 opt_mapping = {
124 min(s_opt.option_strings).lstrip("-").replace("-", "_"): s_opt.dest
125 for s_opt in parser._actions
126 if s_opt.option_strings
127 }
128 arg_options = {opt_mapping.get(key, key): value for key, value in options.items()}
129 parse_args = []
130 for arg in args:
131 if isinstance(arg, (list, tuple)):
132 parse_args += map(str, arg)
133 else:
134 parse_args.append(str(arg))
135
136 def get_actions(parser):
137 # Parser actions and actions from sub-parser choices.
138 for opt in parser._actions:
139 if isinstance(opt, _SubParsersAction):
140 for sub_opt in opt.choices.values():
141 yield from get_actions(sub_opt)
142 else:
143 yield opt
144
145 parser_actions = list(get_actions(parser))
146 mutually_exclusive_required_options = {
147 opt
148 for group in parser._mutually_exclusive_groups
149 for opt in group._group_actions
150 if group.required
151 }
152 # Any required arguments which are passed in via **options must be passed
153 # to parse_args().
154 for opt in parser_actions:
155 if opt.dest in options and (
156 opt.required or opt in mutually_exclusive_required_options
157 ):
158 opt_dest_count = sum(v == opt.dest for v in opt_mapping.values())
159 if opt_dest_count > 1:
160 raise TypeError(
161 f"Cannot pass the dest {opt.dest!r} that matches multiple "
162 f"arguments via **options."
163 )
164 parse_args.append(min(opt.option_strings))
165 if isinstance(opt, (_AppendConstAction, _CountAction, _StoreConstAction)):
166 continue
167 value = arg_options[opt.dest]
168 if isinstance(value, (list, tuple)):
169 parse_args += map(str, value)
170 else:
171 parse_args.append(str(value))
172 defaults = parser.parse_args(args=parse_args)
173 defaults = dict(defaults._get_kwargs(), **arg_options)
174 # Raise an error if any unknown options were passed.
175 stealth_options = set(command.base_stealth_options + command.stealth_options)
176 dest_parameters = {action.dest for action in parser_actions}
177 valid_options = (dest_parameters | stealth_options).union(opt_mapping)
178 unknown_options = set(options) - valid_options
179 if unknown_options:
180 raise TypeError(
181 "Unknown option(s) for %s command: %s. "
182 "Valid options are: %s."
183 % (
184 command_name,
185 ", ".join(sorted(unknown_options)),
186 ", ".join(sorted(valid_options)),
187 )
188 )
189 # Move positional args out of options to mimic legacy optparse
190 args = defaults.pop("args", ())
191 if "skip_checks" not in options:
192 defaults["skip_checks"] = True
193
194 return command.execute(*args, **defaults)
195
196
197class ManagementUtility:
198 """
199 Encapsulate the logic of the django-admin and manage.py utilities.
200 """
201
202 def __init__(self, argv=None):
203 self.argv = argv or sys.argv[:]
204 self.prog_name = os.path.basename(self.argv[0])
205 if self.prog_name == "__main__.py":
206 self.prog_name = "python -m django"
207 self.settings_exception = None
208
209 def main_help_text(self, commands_only=False):
210 """Return the script's main help text, as a string."""
211 if commands_only:
212 usage = sorted(get_commands())
213 else:
214 usage = [
215 "",
216 "Type '%s help <subcommand>' for help on a specific subcommand."
217 % self.prog_name,
218 "",
219 "Available subcommands:",
220 ]
221 commands_dict = defaultdict(lambda: [])
222 for name, app in get_commands().items():
223 if app == "django.core":
224 app = "django"
225 else:
226 app = app.rpartition(".")[-1]
227 commands_dict[app].append(name)
228 style = color_style()
229 for app in sorted(commands_dict):
230 usage.append("")
231 usage.append(style.NOTICE("[%s]" % app))
232 for name in sorted(commands_dict[app]):
233 usage.append(" %s" % name)
234 # Output an extra note if settings are not properly configured
235 if self.settings_exception is not None:
236 usage.append(
237 style.NOTICE(
238 "Note that only Django core commands are listed "
239 "as settings are not properly configured (error: %s)."
240 % self.settings_exception
241 )
242 )
243
244 return "\n".join(usage)
245
246 def fetch_command(self, subcommand):
247 """
248 Try to fetch the given subcommand, printing a message with the
249 appropriate command called from the command line (usually
250 "django-admin" or "manage.py") if it can't be found.
251 """
252 # Get commands outside of try block to prevent swallowing exceptions
253 commands = get_commands()
254 try:
255 app_name = commands[subcommand]
256 except KeyError:
257 if os.environ.get("DJANGO_SETTINGS_MODULE"):
258 # If `subcommand` is missing due to misconfigured settings, the
259 # following line will retrigger an ImproperlyConfigured exception
260 # (get_commands() swallows the original one) so the user is
261 # informed about it.
262 settings.INSTALLED_APPS
263 elif not settings.configured:
264 sys.stderr.write("No Django settings specified.\n")
265 possible_matches = get_close_matches(subcommand, commands)
266 sys.stderr.write("Unknown command: %r" % subcommand)
267 if possible_matches:
268 sys.stderr.write(". Did you mean %s?" % possible_matches[0])
269 sys.stderr.write("\nType '%s help' for usage.\n" % self.prog_name)
270 sys.exit(1)
271 if isinstance(app_name, BaseCommand):
272 # If the command is already loaded, use it directly.
273 klass = app_name
274 else:
275 klass = load_command_class(app_name, subcommand)
276 return klass
277
278 def autocomplete(self):
279 """
280 Output completion suggestions for BASH.
281
282 The output of this function is passed to BASH's `COMPREPLY` variable
283 and treated as completion suggestions. `COMPREPLY` expects a space
284 separated string as the result.
285
286 The `COMP_WORDS` and `COMP_CWORD` BASH environment variables are used
287 to get information about the cli input. Please refer to the BASH
288 man-page for more information about this variables.
289
290 Subcommand options are saved as pairs. A pair consists of
291 the long option string (e.g. '--exclude') and a boolean
292 value indicating if the option requires arguments. When printing to
293 stdout, an equal sign is appended to options which require arguments.
294
295 Note: If debugging this function, it is recommended to write the debug
296 output in a separate file. Otherwise the debug output will be treated
297 and formatted as potential completion suggestions.
298 """
299 # Don't complete if user hasn't sourced bash_completion file.
300 if "DJANGO_AUTO_COMPLETE" not in os.environ:
301 return
302
303 cwords = os.environ["COMP_WORDS"].split()[1:]
304 cword = int(os.environ["COMP_CWORD"])
305
306 try:
307 curr = cwords[cword - 1]
308 except IndexError:
309 curr = ""
310
311 subcommands = [*get_commands(), "help"]
312 options = [("--help", False)]
313
314 # subcommand
315 if cword == 1:
316 print(" ".join(sorted(filter(lambda x: x.startswith(curr), subcommands))))
317 # subcommand options
318 # special case: the 'help' subcommand has no options
319 elif cwords[0] in subcommands and cwords[0] != "help":
320 subcommand_cls = self.fetch_command(cwords[0])
321 # special case: add the names of installed apps to options
322 if cwords[0] in ("dumpdata", "sqlmigrate", "sqlsequencereset", "test"):
323 try:
324 app_configs = apps.get_app_configs()
325 # Get the last part of the dotted path as the app name.
326 options.extend((app_config.label, 0) for app_config in app_configs)
327 except ImportError:
328 # Fail silently if DJANGO_SETTINGS_MODULE isn't set. The
329 # user will find out once they execute the command.
330 pass
331 parser = subcommand_cls.create_parser("", cwords[0])
332 options.extend(
333 (min(s_opt.option_strings), s_opt.nargs != 0)
334 for s_opt in parser._actions
335 if s_opt.option_strings
336 )
337 # filter out previously specified options from available options
338 prev_opts = {x.split("=")[0] for x in cwords[1 : cword - 1]}
339 options = (opt for opt in options if opt[0] not in prev_opts)
340
341 # filter options by current input
342 options = sorted((k, v) for k, v in options if k.startswith(curr))
343 for opt_label, require_arg in options:
344 # append '=' to options which require args
345 if require_arg:
346 opt_label += "="
347 print(opt_label)
348 # Exit code of the bash completion function is never passed back to
349 # the user, so it's safe to always exit with 0.
350 # For more details see #25420.
351 sys.exit(0)
352
353 def execute(self):
354 """
355 Given the command-line arguments, figure out which subcommand is being
356 run, create a parser appropriate to that command, and run it.
357 """
358 try:
359 subcommand = self.argv[1]
360 except IndexError:
361 subcommand = "help" # Display help if no arguments were given.
362
363 # Preprocess options to extract --settings and --pythonpath.
364 # These options could affect the commands that are available, so they
365 # must be processed early.
366 parser = CommandParser(
367 prog=self.prog_name,
368 usage="%(prog)s subcommand [options] [args]",
369 add_help=False,
370 allow_abbrev=False,
371 )
372 parser.add_argument("--settings")
373 parser.add_argument("--pythonpath")
374 parser.add_argument("args", nargs="*") # catch-all
375 try:
376 options, args = parser.parse_known_args(self.argv[2:])
377 handle_default_options(options)
378 except CommandError:
379 pass # Ignore any option errors at this point.
380
381 try:
382 settings.INSTALLED_APPS
383 except ImproperlyConfigured as exc:
384 self.settings_exception = exc
385 except ImportError as exc:
386 self.settings_exception = exc
387
388 if settings.configured:
389 # Start the auto-reloading dev server even if the code is broken.
390 # The hardcoded condition is a code smell but we can't rely on a
391 # flag on the command class because we haven't located it yet.
392 if subcommand == "runserver" and "--noreload" not in self.argv:
393 try:
394 autoreload.check_errors(django.setup)()
395 except Exception:
396 # The exception will be raised later in the child process
397 # started by the autoreloader. Pretend it didn't happen by
398 # loading an empty list of applications.
399 apps.all_models = defaultdict(dict)
400 apps.app_configs = {}
401 apps.apps_ready = apps.models_ready = apps.ready = True
402
403 # Remove options not compatible with the built-in runserver
404 # (e.g. options for the contrib.staticfiles' runserver).
405 # Changes here require manually testing as described in
406 # #27522.
407 _parser = self.fetch_command("runserver").create_parser(
408 "django", "runserver"
409 )
410 _options, _args = _parser.parse_known_args(self.argv[2:])
411 for _arg in _args:
412 self.argv.remove(_arg)
413
414 # In all other cases, django.setup() is required to succeed.
415 else:
416 django.setup()
417
418 self.autocomplete()
419
420 if subcommand == "help":
421 if "--commands" in args:
422 sys.stdout.write(self.main_help_text(commands_only=True) + "\n")
423 elif not options.args:
424 sys.stdout.write(self.main_help_text() + "\n")
425 else:
426 self.fetch_command(options.args[0]).print_help(
427 self.prog_name, options.args[0]
428 )
429 # Special-cases: We want 'django-admin --version' and
430 # 'django-admin --help' to work, for backwards compatibility.
431 elif subcommand == "version" or self.argv[1:] == ["--version"]:
432 sys.stdout.write(django.get_version() + "\n")
433 elif self.argv[1:] in (["--help"], ["-h"]):
434 sys.stdout.write(self.main_help_text() + "\n")
435 else:
436 self.fetch_command(subcommand).run_from_argv(self.argv)
437
438
439def execute_from_command_line(argv=None):
440 """Run a ManagementUtility."""
441 utility = ManagementUtility(argv)
442 utility.execute()