Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.10/site-packages/django/core/management/__init__.py: 13%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

205 statements  

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()