Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/argcomplete/finders.py: 17%
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
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
1# Copyright 2012-2023, Andrey Kislyuk and argcomplete contributors. Licensed under the terms of the
2# `Apache License, Version 2.0 <http://www.apache.org/licenses/LICENSE-2.0>`_. Distribution of the LICENSE and NOTICE
3# files with source copies of this package and derivative works is **REQUIRED** as specified by the Apache License.
4# See https://github.com/kislyuk/argcomplete for more info.
6from __future__ import annotations
8import argparse
9import os
10import sys
11from collections.abc import Callable, Container, Mapping
12from typing import Literal, TextIO
14from . import io as _io
15from .completers import BaseCompleter, ChoicesCompleter, FilesCompleter, SuppressCompleter
16from .io import debug, mute_stderr
17from .lexers import split_line
18from .packages._argparse import IntrospectiveArgumentParser, action_is_greedy, action_is_open, action_is_satisfied
20safe_actions = {
21 argparse._StoreAction,
22 argparse._StoreConstAction,
23 argparse._StoreTrueAction,
24 argparse._StoreFalseAction,
25 argparse._AppendAction,
26 argparse._AppendConstAction,
27 argparse._CountAction,
28}
31def default_validator(completion: str, prefix: str) -> bool:
32 return completion.startswith(prefix)
35class CompletionFinder:
36 """
37 Inherit from this class if you wish to override any of the stages below. Otherwise, use
38 ``argcomplete.autocomplete()`` directly (it's a convenience instance of this class). It has the same signature as
39 :meth:`CompletionFinder.__call__()`.
40 """
42 _parser: argparse.ArgumentParser | None
43 _formatter: argparse.HelpFormatter | None
44 always_complete_options: bool | Literal["long", "short"]
45 exclude: Container[str] | None
46 validator: Callable[[str, str], bool]
47 print_suppressed: bool
48 completing: bool
49 _display_completions: dict[str, str]
50 default_completer: BaseCompleter
51 append_space: bool
53 active_parsers: list[argparse.ArgumentParser]
54 visited_positionals: list[argparse.Action]
56 def __init__(
57 self,
58 argument_parser: argparse.ArgumentParser | None = None,
59 always_complete_options: bool | Literal["long", "short"] = True,
60 exclude: Container[str] | None = None,
61 validator: Callable[[str, str], bool] | None = None,
62 print_suppressed: bool = False,
63 default_completer: BaseCompleter = FilesCompleter(),
64 append_space: bool | None = None,
65 ) -> None:
66 self._parser = argument_parser # type: ignore[assignment]
67 self._formatter = None
68 self.always_complete_options = always_complete_options
69 self.exclude = exclude
70 if validator is None:
71 validator = default_validator
72 self.validator = validator
73 self.print_suppressed = print_suppressed
74 self.completing = False
75 self._display_completions = {}
76 self.default_completer = default_completer
77 if append_space is None:
78 append_space = os.environ.get("_ARGCOMPLETE_SUPPRESS_SPACE") != "1"
79 self.append_space = append_space
81 def __call__(
82 self,
83 argument_parser: argparse.ArgumentParser,
84 always_complete_options: bool | str = True,
85 exit_method: Callable = os._exit,
86 output_stream: TextIO | None = None,
87 exclude: Container[str] | None = None,
88 validator: Callable[[str, str], bool] | None = None,
89 print_suppressed: bool = False,
90 append_space: bool | None = None,
91 default_completer: BaseCompleter = FilesCompleter(),
92 ) -> None:
93 """
94 :param argument_parser: The argument parser to autocomplete on
95 :param always_complete_options:
96 Controls the autocompletion of option strings if an option string opening character (normally ``-``) has not
97 been entered. If ``True`` (default), both short (``-x``) and long (``--x``) option strings will be
98 suggested. If ``False``, no option strings will be suggested. If ``long``, long options and short options
99 with no long variant will be suggested. If ``short``, short options and long options with no short variant
100 will be suggested.
101 :param exit_method:
102 Method used to stop the program after printing completions. Defaults to :meth:`os._exit`. If you want to
103 perform a normal exit that calls exit handlers, use :meth:`sys.exit`.
104 :param exclude: List of strings representing options to be omitted from autocompletion
105 :param validator:
106 Function to filter all completions through before returning (called with two string arguments, completion
107 and prefix; return value is evaluated as a boolean)
108 :param print_suppressed:
109 Whether or not to autocomplete options that have the ``help=argparse.SUPPRESS`` keyword argument set.
110 :param append_space:
111 Whether to append a space to unique matches. The default is ``True``.
113 .. note::
114 If you are not subclassing CompletionFinder to override its behaviors,
115 use :meth:`argcomplete.autocomplete()` directly. It has the same signature as this method.
117 Produces tab completions for ``argument_parser``. See module docs for more info.
119 Argcomplete only executes actions if their class is known not to have side effects. Custom action classes can be
120 added to argcomplete.safe_actions, if their values are wanted in the ``parsed_args`` completer argument, or
121 their execution is otherwise desirable.
122 """
123 self.__init__( # type: ignore
124 argument_parser,
125 always_complete_options=always_complete_options,
126 exclude=exclude,
127 validator=validator,
128 print_suppressed=print_suppressed,
129 append_space=append_space,
130 default_completer=default_completer,
131 )
133 if "_ARGCOMPLETE" not in os.environ:
134 # not an argument completion invocation
135 return
137 self._init_debug_stream()
139 if output_stream is None:
140 filename = os.environ.get("_ARGCOMPLETE_STDOUT_FILENAME")
141 if filename is not None:
142 debug(f"Using output file {filename}")
143 encoding = "utf-8" if os.environ.get("_ARGCOMPLETE_SHELL") == "powershell" else None
144 output_stream = open(filename, "w", encoding=encoding)
146 if output_stream is None:
147 try:
148 output_stream = os.fdopen(8, "w")
149 except Exception:
150 debug("Unable to open fd 8 for writing, quitting")
151 exit_method(1)
153 assert output_stream is not None
155 ifs = os.environ.get("_ARGCOMPLETE_IFS", "\013")
156 if len(ifs) != 1:
157 debug(f"Invalid value for IFS, quitting [{ifs}]")
158 exit_method(1)
160 dfs = os.environ.get("_ARGCOMPLETE_DFS")
161 if dfs and len(dfs) != 1:
162 debug(f"Invalid value for DFS, quitting [{dfs}]")
163 exit_method(1)
165 comp_line = os.environ["COMP_LINE"]
166 try:
167 comp_point = int(os.environ["COMP_POINT"])
168 except ValueError:
169 debug("Invalid value for COMP_POINT, quitting [{v}]".format(v=os.environ["COMP_POINT"]))
170 exit_method(1)
171 return
173 cword_prequote, cword_prefix, cword_suffix, comp_words, last_wordbreak_pos = split_line(comp_line, comp_point)
175 # _ARGCOMPLETE is set by the shell script to tell us where comp_words
176 # should start, based on what we're completing.
177 # 1: <script> [args]
178 # 2: python <script> [args]
179 # 3: python -m <module> [args]
180 start = int(os.environ["_ARGCOMPLETE"]) - 1
181 comp_words = comp_words[start:]
183 assert self._parser is not None
184 if cword_prefix and cword_prefix[0] in self._parser.prefix_chars and "=" in cword_prefix:
185 # Special case for when the current word is "--optional=PARTIAL_VALUE". Give the optional to the parser.
186 comp_words.append(cword_prefix.split("=", 1)[0])
188 debug(
189 f"\nLINE: {comp_line!r}",
190 f"\nPOINT: {comp_point!r}",
191 f"\nPREQUOTE: {cword_prequote!r}",
192 f"\nPREFIX: {cword_prefix!r}",
193 f"\nSUFFIX: {cword_suffix!r}",
194 "\nWORDS:",
195 comp_words,
196 )
198 completions = self._get_completions(comp_words, cword_prefix, cword_prequote, last_wordbreak_pos)
200 if dfs:
201 display_completions = {
202 key: value.replace(ifs, " ") if value else "" for key, value in self._display_completions.items()
203 }
204 completions = [dfs.join((key, display_completions.get(key) or "")) for key in completions]
206 if os.environ.get("_ARGCOMPLETE_SHELL") == "zsh":
207 completions = [f"{c}:{(self._display_completions.get(c) or '').replace(ifs, ' ')}" for c in completions]
209 debug("\nReturning completions:", completions)
210 output_stream.write(ifs.join(completions))
211 output_stream.flush()
212 _io.debug_stream.flush()
213 exit_method(0)
215 def _init_debug_stream(self):
216 """Initialize debug output stream
218 By default, writes to file descriptor 9, or stderr if that fails.
219 This can be overridden by derived classes, for example to avoid
220 clashes with file descriptors being used elsewhere (such as in pytest).
221 """
222 try:
223 _io.debug_stream = os.fdopen(9, "w")
224 except Exception:
225 _io.debug_stream = sys.stderr
226 debug()
228 def _get_completions(self, comp_words, cword_prefix, cword_prequote, last_wordbreak_pos):
229 active_parsers = self._patch_argument_parser()
231 parsed_args = argparse.Namespace()
232 self.completing = True
234 try:
235 debug("invoking parser with", comp_words[1:])
236 with mute_stderr():
237 assert self._parser is not None
238 a = self._parser.parse_known_args(comp_words[1:], namespace=parsed_args)
239 debug("parsed args:", a)
240 except BaseException as e:
241 debug("\nexception", type(e), str(e), "while parsing args")
243 self.completing = False
245 if "--" in comp_words:
246 self.always_complete_options = False
248 completions = self.collect_completions(active_parsers, parsed_args, cword_prefix)
249 completions = self.filter_completions(completions)
250 completions = self.quote_completions(completions, cword_prequote, last_wordbreak_pos)
251 return completions
253 def _patch_argument_parser(self):
254 """
255 Since argparse doesn't support much introspection, we monkey-patch it to replace the parse_known_args method and
256 all actions with hooks that tell us which action was last taken or about to be taken, and let us have the parser
257 figure out which subparsers need to be activated (then recursively monkey-patch those).
258 We save all active ArgumentParsers to extract all their possible option names later.
259 """
260 self.active_parsers = []
261 self.visited_positionals = []
263 completer = self
265 def patch(parser):
266 completer.visited_positionals.append(parser)
267 completer.active_parsers.append(parser)
269 if isinstance(parser, IntrospectiveArgumentParser):
270 return
272 classname = "MonkeyPatchedIntrospectiveArgumentParser"
274 parser.__class__ = type(classname, (IntrospectiveArgumentParser, parser.__class__), {})
276 for action in parser._actions:
277 if hasattr(action, "_orig_class"):
278 continue
280 # TODO: accomplish this with super
281 class IntrospectAction(action.__class__): # type: ignore
282 def __call__(self, parser, namespace, values, option_string=None):
283 debug("Action stub called on", self)
284 debug("\targs:", parser, namespace, values, option_string)
285 debug("\torig class:", self._orig_class)
286 debug("\torig callable:", self._orig_callable)
288 if not completer.completing:
289 self._orig_callable(parser, namespace, values, option_string=option_string)
290 elif issubclass(self._orig_class, argparse._SubParsersAction):
291 debug("orig class is a subparsers action: patching and running it")
292 patch(self._name_parser_map[values[0]])
293 self._orig_callable(parser, namespace, values, option_string=option_string)
294 elif self._orig_class in safe_actions:
295 if not self.option_strings:
296 completer.visited_positionals.append(self)
298 self._orig_callable(parser, namespace, values, option_string=option_string)
300 action._orig_class = action.__class__
301 action._orig_callable = action.__call__
302 action.__class__ = IntrospectAction
304 patch(self._parser)
306 debug("Active parsers:", self.active_parsers)
307 debug("Visited positionals:", self.visited_positionals)
309 return self.active_parsers
311 def _get_action_help(self, action):
312 if action.help is None:
313 return ""
314 if "%" not in action.help:
315 return action.help
316 formatters = self._formatter
317 if formatters is None:
318 assert self._parser is not None
319 self._formatter = formatters = self._parser.formatter_class(prog=self._parser.prog)
320 return formatters._expand_help(action)
322 def _get_subparser_completions(self, parser, cword_prefix):
323 aliases_by_parser: dict[argparse.ArgumentParser, list[str]] = {}
324 for key in parser.choices:
325 p = parser.choices[key]
326 aliases_by_parser.setdefault(p, []).append(key)
328 for action in parser._get_subactions():
329 for alias in aliases_by_parser[parser.choices[action.dest]]:
330 if alias.startswith(cword_prefix):
331 self._display_completions[alias] = self._get_action_help(action)
333 completions = [subcmd for subcmd in parser.choices if subcmd.startswith(cword_prefix)]
334 return completions
336 def _include_options(self, action, cword_prefix):
337 if len(cword_prefix) > 0 or self.always_complete_options is True:
338 return [opt for opt in action.option_strings if opt.startswith(cword_prefix)]
339 long_opts = [opt for opt in action.option_strings if len(opt) > 2]
340 short_opts = [opt for opt in action.option_strings if len(opt) <= 2]
341 if self.always_complete_options == "long":
342 return long_opts if long_opts else short_opts
343 elif self.always_complete_options == "short":
344 return short_opts if short_opts else long_opts
345 return []
347 def _get_option_completions(self, parser, cword_prefix):
348 for action in parser._actions:
349 if action.option_strings:
350 for option_string in action.option_strings:
351 if option_string.startswith(cword_prefix):
352 self._display_completions[option_string] = self._get_action_help(action)
354 option_completions = []
355 for action in parser._actions:
356 if not self.print_suppressed:
357 completer = getattr(action, "completer", None)
358 if isinstance(completer, SuppressCompleter) and completer.suppress():
359 continue
360 if action.help == argparse.SUPPRESS:
361 continue
362 if not self._action_allowed(action, parser):
363 continue
364 if not isinstance(action, argparse._SubParsersAction):
365 option_completions += self._include_options(action, cword_prefix)
366 return option_completions
368 @staticmethod
369 def _action_allowed(action, parser):
370 # Logic adapted from take_action in ArgumentParser._parse_known_args
371 # (members are saved by vendor._argparse.IntrospectiveArgumentParser)
372 for conflict_action in parser._action_conflicts.get(action, []):
373 if conflict_action in parser._seen_non_default_actions:
374 return False
375 return True
377 def _complete_active_option(self, parser, next_positional, cword_prefix, parsed_args, completions):
378 debug(f"Active actions (L={len(parser.active_actions)}): {parser.active_actions}")
380 isoptional = cword_prefix and cword_prefix[0] in parser.prefix_chars
381 optional_prefix = ""
382 greedy_actions = [x for x in parser.active_actions if action_is_greedy(x, isoptional)]
383 if greedy_actions:
384 assert len(greedy_actions) == 1, "expect at most 1 greedy action"
385 # This means the action will fail to parse if the word under the cursor is not given
386 # to it, so give it exclusive control over completions (flush previous completions)
387 debug("Resetting completions because", greedy_actions[0], "must consume the next argument")
388 self._display_completions = {}
389 completions = []
390 elif isoptional:
391 if "=" in cword_prefix:
392 # Special case for when the current word is "--optional=PARTIAL_VALUE".
393 # The completer runs on PARTIAL_VALUE. The prefix is added back to the completions
394 # (and chopped back off later in quote_completions() by the COMP_WORDBREAKS logic).
395 optional_prefix, _, cword_prefix = cword_prefix.partition("=")
396 else:
397 # Only run completers if current word does not start with - (is not an optional)
398 return completions
400 complete_remaining_positionals = False
401 # Use the single greedy action (if there is one) or all active actions.
402 for active_action in greedy_actions or parser.active_actions:
403 if not active_action.option_strings: # action is a positional
404 if action_is_open(active_action):
405 # Any positional arguments after this may slide down into this action
406 # if more arguments are added (since the user may not be done yet),
407 # so it is extremely difficult to tell which completers to run.
408 # Running all remaining completers will probably show more than the user wants
409 # but it also guarantees we won't miss anything.
410 complete_remaining_positionals = True
411 if not complete_remaining_positionals:
412 if action_is_satisfied(active_action) and not action_is_open(active_action):
413 debug("Skipping", active_action)
414 continue
416 debug("Activating completion for", active_action, active_action._orig_class)
417 # completer = getattr(active_action, "completer", DefaultCompleter())
418 completer = getattr(active_action, "completer", None)
420 if completer is None:
421 if active_action.choices is not None and not isinstance(active_action, argparse._SubParsersAction):
422 completer = ChoicesCompleter(active_action.choices)
423 elif not isinstance(active_action, argparse._SubParsersAction):
424 completer = self.default_completer
426 if completer:
427 if isinstance(completer, SuppressCompleter) and completer.suppress():
428 continue
430 if callable(completer):
431 completer_output = completer(
432 prefix=cword_prefix, action=active_action, parser=parser, parsed_args=parsed_args
433 )
434 if isinstance(completer_output, Mapping):
435 for completion, description in completer_output.items():
436 if self.validator(completion, cword_prefix):
437 completions.append(completion)
438 self._display_completions[completion] = description
439 else:
440 for completion in completer_output:
441 if self.validator(completion, cword_prefix):
442 completions.append(completion)
443 if isinstance(completer, ChoicesCompleter):
444 self._display_completions[completion] = self._get_action_help(active_action)
445 else:
446 self._display_completions[completion] = ""
447 else:
448 debug("Completer is not callable, trying the readline completer protocol instead")
449 for i in range(9999):
450 next_completion = completer.complete(cword_prefix, i) # type: ignore
451 if next_completion is None:
452 break
453 if self.validator(next_completion, cword_prefix):
454 self._display_completions[next_completion] = ""
455 completions.append(next_completion)
456 if optional_prefix:
457 completions = [optional_prefix + "=" + completion for completion in completions]
458 debug("Completions:", completions)
459 return completions
461 def collect_completions(
462 self, active_parsers: list[argparse.ArgumentParser], parsed_args: argparse.Namespace, cword_prefix: str
463 ) -> list[str]:
464 """
465 Visits the active parsers and their actions, executes their completers or introspects them to collect their
466 option strings. Returns the resulting completions as a list of strings.
468 This method is exposed for overriding in subclasses; there is no need to use it directly.
469 """
470 completions: list[str] = []
472 debug("all active parsers:", active_parsers)
473 active_parser = active_parsers[-1]
474 debug("active_parser:", active_parser)
475 if self.always_complete_options or (len(cword_prefix) > 0 and cword_prefix[0] in active_parser.prefix_chars):
476 completions += self._get_option_completions(active_parser, cword_prefix)
477 debug("optional options:", completions)
479 next_positional = self._get_next_positional()
480 debug("next_positional:", next_positional)
482 if isinstance(next_positional, argparse._SubParsersAction):
483 completions += self._get_subparser_completions(next_positional, cword_prefix)
485 completions = self._complete_active_option(
486 active_parser, next_positional, cword_prefix, parsed_args, completions
487 )
488 debug("active options:", completions)
489 debug("display completions:", self._display_completions)
491 return completions
493 def _get_next_positional(self):
494 """
495 Get the next positional action if it exists.
496 """
497 active_parser = self.active_parsers[-1]
498 last_positional = self.visited_positionals[-1]
500 all_positionals = active_parser._get_positional_actions()
501 if not all_positionals:
502 return None
504 if active_parser == last_positional:
505 return all_positionals[0]
507 i = 0
508 for i in range(len(all_positionals)):
509 if all_positionals[i] == last_positional:
510 break
512 if i + 1 < len(all_positionals):
513 return all_positionals[i + 1]
515 return None
517 def filter_completions(self, completions: list[str]) -> list[str]:
518 """
519 De-duplicates completions and excludes those specified by ``exclude``.
520 Returns the filtered completions as a list.
522 This method is exposed for overriding in subclasses; there is no need to use it directly.
523 """
524 filtered_completions = []
525 for completion in completions:
526 if self.exclude is not None and completion in self.exclude:
527 continue
528 if completion not in filtered_completions:
529 filtered_completions.append(completion)
530 return filtered_completions
532 def quote_completions(
533 self, completions: list[str], cword_prequote: str, last_wordbreak_pos: int | None
534 ) -> list[str]:
535 """
536 If the word under the cursor started with a quote (as indicated by a nonempty ``cword_prequote``), escapes
537 occurrences of that quote character in the completions, and adds the quote to the beginning of each completion.
538 Otherwise, escapes all characters that bash splits words on (``COMP_WORDBREAKS``), and removes portions of
539 completions before the first colon if (``COMP_WORDBREAKS``) contains a colon.
541 If there is only one completion, and it doesn't end with a **continuation character** (``/``, ``:``, or ``=``),
542 adds a space after the completion.
544 This method is exposed for overriding in subclasses; there is no need to use it directly.
545 """
546 if os.environ.get("_ARGCOMPLETE_SHELL") == "powershell":
547 # PowerShell replaces the whole token, including any opening quote.
548 # Its shell integration quotes these raw values with PowerShell's own escaping API.
549 return completions
551 special_chars = "\\"
552 # If the word under the cursor was quoted, escape the quote char.
553 # Otherwise, escape all special characters and specially handle all COMP_WORDBREAKS chars.
554 if cword_prequote == "":
555 # Bash mangles completions which contain characters in COMP_WORDBREAKS.
556 # This workaround has the same effect as __ltrim_colon_completions in bash_completion
557 # (extended to characters other than the colon).
558 if last_wordbreak_pos is not None:
559 completions = [c[last_wordbreak_pos + 1 :] for c in completions]
560 special_chars += "();<>|&!`$*?[]{} \t\n\"'"
561 elif cword_prequote == '"':
562 special_chars += '"`$!'
564 if os.environ.get("_ARGCOMPLETE_SHELL") in ("tcsh", "fish"):
565 # tcsh and fish escapes special characters itself.
566 special_chars = ""
567 elif cword_prequote == "'":
568 # Nothing can be escaped in single quotes, so we need to close
569 # the string, escape the single quote, then open a new string.
570 special_chars = ""
571 completions = [c.replace("'", r"'\''") for c in completions]
573 escape_char = "\\"
574 if os.environ.get("_ARGCOMPLETE_SHELL") == "zsh":
575 # zsh uses colon as a separator between a completion and its description.
576 special_chars += ":"
578 escaped_completions = []
579 for completion in completions:
580 escaped_completion = completion
581 for char in special_chars:
582 escaped_completion = escaped_completion.replace(char, escape_char + char)
583 escaped_completions.append(escaped_completion)
584 if completion in self._display_completions:
585 self._display_completions[escaped_completion] = self._display_completions[completion]
587 if self.append_space:
588 # Similar functionality in bash was previously turned off by supplying the "-o nospace" option to complete.
589 # Now it is conditionally disabled using "compopt -o nospace" if the match ends in a continuation character.
590 # This code is retained for environments where this isn't done natively.
591 continuation_chars = "=/:"
592 if (
593 len(escaped_completions) == 1
594 and escaped_completions[0]
595 and escaped_completions[0][-1] not in continuation_chars
596 ):
597 if cword_prequote == "":
598 escaped_completions[0] += " "
600 return escaped_completions
602 def rl_complete(self, text: str, state: int) -> str | None:
603 """
604 Alternate entry point for using the argcomplete completer in a readline-based REPL. See also
605 `rlcompleter <https://docs.python.org/3/library/rlcompleter.html#completer-objects>`_.
606 Usage:
608 .. code-block:: python
610 import argcomplete, argparse, readline
611 parser = argparse.ArgumentParser()
612 ...
613 completer = argcomplete.CompletionFinder(parser)
614 readline.set_completer_delims("")
615 readline.set_completer(completer.rl_complete)
616 readline.parse_and_bind("tab: complete")
617 result = input("prompt> ")
618 """
619 if state == 0:
620 cword_prequote, cword_prefix, _cword_suffix, comp_words, first_colon_pos = split_line(text)
621 comp_words.insert(0, sys.argv[0])
622 matches = self._get_completions(comp_words, cword_prefix, cword_prequote, first_colon_pos)
623 self._rl_matches = [text + match[len(cword_prefix) :] for match in matches]
625 if state < len(self._rl_matches):
626 return self._rl_matches[state]
627 else:
628 return None
630 def get_display_completions(self) -> dict[str, str]:
631 """
632 This function returns a mapping of completions to their help strings for displaying to the user.
633 """
634 return self._display_completions
637class ExclusiveCompletionFinder(CompletionFinder):
638 @staticmethod
639 def _action_allowed(action, parser):
640 if not CompletionFinder._action_allowed(action, parser):
641 return False
643 append_classes = (argparse._AppendAction, argparse._AppendConstAction)
644 if action._orig_class in append_classes:
645 return True
647 return action not in parser._seen_non_default_actions