cmd2 4.1.2__tar.gz → 4.2.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (222) hide show
  1. {cmd2-4.1.2 → cmd2-4.2.1}/.pre-commit-config.yaml +2 -2
  2. {cmd2-4.1.2 → cmd2-4.2.1}/CHANGELOG.md +38 -0
  3. {cmd2-4.1.2 → cmd2-4.2.1}/Makefile +9 -9
  4. {cmd2-4.1.2 → cmd2-4.2.1}/PKG-INFO +2 -2
  5. {cmd2-4.1.2 → cmd2-4.2.1}/cmd2/annotated.py +72 -51
  6. {cmd2-4.1.2 → cmd2-4.2.1}/cmd2/argparse_completer.py +4 -2
  7. {cmd2-4.1.2 → cmd2-4.2.1}/cmd2/argparse_utils.py +7 -6
  8. {cmd2-4.1.2 → cmd2-4.2.1}/cmd2/cmd2.py +67 -38
  9. {cmd2-4.1.2 → cmd2-4.2.1}/cmd2/decorators.py +3 -3
  10. {cmd2-4.1.2 → cmd2-4.2.1}/cmd2/parsing.py +4 -4
  11. {cmd2-4.1.2 → cmd2-4.2.1}/cmd2/pt_utils.py +1 -1
  12. {cmd2-4.1.2 → cmd2-4.2.1}/cmd2/rich_utils.py +1 -1
  13. {cmd2-4.1.2 → cmd2-4.2.1}/cmd2.egg-info/PKG-INFO +2 -2
  14. {cmd2-4.1.2 → cmd2-4.2.1}/cmd2.egg-info/SOURCES.txt +1 -0
  15. {cmd2-4.1.2 → cmd2-4.2.1}/cmd2.egg-info/requires.txt +1 -1
  16. {cmd2-4.1.2 → cmd2-4.2.1}/cmd2.egg-info/scm_file_list.json +127 -126
  17. cmd2-4.2.1/cmd2.egg-info/scm_version.json +8 -0
  18. {cmd2-4.1.2 → cmd2-4.2.1}/docs/features/annotated.md +35 -10
  19. {cmd2-4.1.2 → cmd2-4.2.1}/docs/features/argument_processing.md +8 -8
  20. {cmd2-4.1.2 → cmd2-4.2.1}/docs/upgrades.md +9 -9
  21. {cmd2-4.1.2 → cmd2-4.2.1}/examples/annotated_example.py +35 -0
  22. {cmd2-4.1.2 → cmd2-4.2.1}/examples/modular_commandsets.py +3 -2
  23. {cmd2-4.1.2 → cmd2-4.2.1}/pyproject.toml +3 -29
  24. {cmd2-4.1.2 → cmd2-4.2.1}/ruff.toml +2 -1
  25. {cmd2-4.1.2 → cmd2-4.2.1}/tests/test_annotated.py +216 -31
  26. {cmd2-4.1.2 → cmd2-4.2.1}/tests/test_argparse_completer.py +52 -0
  27. {cmd2-4.1.2 → cmd2-4.2.1}/tests/test_cmd2.py +91 -1
  28. cmd2-4.2.1/ty.toml +8 -0
  29. cmd2-4.1.2/cmd2.egg-info/scm_version.json +0 -8
  30. {cmd2-4.1.2 → cmd2-4.2.1}/.prettierignore +0 -0
  31. {cmd2-4.1.2 → cmd2-4.2.1}/.prettierrc +0 -0
  32. {cmd2-4.1.2 → cmd2-4.2.1}/.typos.toml +0 -0
  33. {cmd2-4.1.2 → cmd2-4.2.1}/GEMINI.md +0 -0
  34. {cmd2-4.1.2 → cmd2-4.2.1}/LICENSE +0 -0
  35. {cmd2-4.1.2 → cmd2-4.2.1}/MANIFEST.in +0 -0
  36. {cmd2-4.1.2 → cmd2-4.2.1}/README.md +0 -0
  37. {cmd2-4.1.2 → cmd2-4.2.1}/cmd2/__init__.py +0 -0
  38. {cmd2-4.1.2 → cmd2-4.2.1}/cmd2/clipboard.py +0 -0
  39. {cmd2-4.1.2 → cmd2-4.2.1}/cmd2/colors.py +0 -0
  40. {cmd2-4.1.2 → cmd2-4.2.1}/cmd2/command_set.py +0 -0
  41. {cmd2-4.1.2 → cmd2-4.2.1}/cmd2/completion.py +0 -0
  42. {cmd2-4.1.2 → cmd2-4.2.1}/cmd2/constants.py +0 -0
  43. {cmd2-4.1.2 → cmd2-4.2.1}/cmd2/exceptions.py +0 -0
  44. {cmd2-4.1.2 → cmd2-4.2.1}/cmd2/history.py +0 -0
  45. {cmd2-4.1.2 → cmd2-4.2.1}/cmd2/plugin.py +0 -0
  46. {cmd2-4.1.2 → cmd2-4.2.1}/cmd2/py.typed +0 -0
  47. {cmd2-4.1.2 → cmd2-4.2.1}/cmd2/py_bridge.py +0 -0
  48. {cmd2-4.1.2 → cmd2-4.2.1}/cmd2/string_utils.py +0 -0
  49. {cmd2-4.1.2 → cmd2-4.2.1}/cmd2/styles.py +0 -0
  50. {cmd2-4.1.2 → cmd2-4.2.1}/cmd2/theme.py +0 -0
  51. {cmd2-4.1.2 → cmd2-4.2.1}/cmd2/types.py +0 -0
  52. {cmd2-4.1.2 → cmd2-4.2.1}/cmd2/utils.py +0 -0
  53. {cmd2-4.1.2 → cmd2-4.2.1}/cmd2.egg-info/dependency_links.txt +0 -0
  54. {cmd2-4.1.2 → cmd2-4.2.1}/cmd2.egg-info/top_level.txt +0 -0
  55. {cmd2-4.1.2 → cmd2-4.2.1}/cmd2.png +0 -0
  56. {cmd2-4.1.2 → cmd2-4.2.1}/codecov.yml +0 -0
  57. {cmd2-4.1.2 → cmd2-4.2.1}/docs/api/annotated.md +0 -0
  58. {cmd2-4.1.2 → cmd2-4.2.1}/docs/api/argparse_completer.md +0 -0
  59. {cmd2-4.1.2 → cmd2-4.2.1}/docs/api/argparse_utils.md +0 -0
  60. {cmd2-4.1.2 → cmd2-4.2.1}/docs/api/clipboard.md +0 -0
  61. {cmd2-4.1.2 → cmd2-4.2.1}/docs/api/cmd.md +0 -0
  62. {cmd2-4.1.2 → cmd2-4.2.1}/docs/api/colors.md +0 -0
  63. {cmd2-4.1.2 → cmd2-4.2.1}/docs/api/command_set.md +0 -0
  64. {cmd2-4.1.2 → cmd2-4.2.1}/docs/api/completion.md +0 -0
  65. {cmd2-4.1.2 → cmd2-4.2.1}/docs/api/constants.md +0 -0
  66. {cmd2-4.1.2 → cmd2-4.2.1}/docs/api/decorators.md +0 -0
  67. {cmd2-4.1.2 → cmd2-4.2.1}/docs/api/exceptions.md +0 -0
  68. {cmd2-4.1.2 → cmd2-4.2.1}/docs/api/history.md +0 -0
  69. {cmd2-4.1.2 → cmd2-4.2.1}/docs/api/index.md +0 -0
  70. {cmd2-4.1.2 → cmd2-4.2.1}/docs/api/parsing.md +0 -0
  71. {cmd2-4.1.2 → cmd2-4.2.1}/docs/api/plugin.md +0 -0
  72. {cmd2-4.1.2 → cmd2-4.2.1}/docs/api/pt_utils.md +0 -0
  73. {cmd2-4.1.2 → cmd2-4.2.1}/docs/api/py_bridge.md +0 -0
  74. {cmd2-4.1.2 → cmd2-4.2.1}/docs/api/rich_utils.md +0 -0
  75. {cmd2-4.1.2 → cmd2-4.2.1}/docs/api/string_utils.md +0 -0
  76. {cmd2-4.1.2 → cmd2-4.2.1}/docs/api/styles.md +0 -0
  77. {cmd2-4.1.2 → cmd2-4.2.1}/docs/api/theme.md +0 -0
  78. {cmd2-4.1.2 → cmd2-4.2.1}/docs/api/utils.md +0 -0
  79. {cmd2-4.1.2 → cmd2-4.2.1}/docs/doc_conventions.md +0 -0
  80. {cmd2-4.1.2 → cmd2-4.2.1}/docs/examples/alternate_event_loops.md +0 -0
  81. {cmd2-4.1.2 → cmd2-4.2.1}/docs/examples/examples.md +0 -0
  82. {cmd2-4.1.2 → cmd2-4.2.1}/docs/examples/getting_started.md +0 -0
  83. {cmd2-4.1.2 → cmd2-4.2.1}/docs/examples/index.md +0 -0
  84. {cmd2-4.1.2 → cmd2-4.2.1}/docs/features/async_commands.md +0 -0
  85. {cmd2-4.1.2 → cmd2-4.2.1}/docs/features/builtin_commands.md +0 -0
  86. {cmd2-4.1.2 → cmd2-4.2.1}/docs/features/clipboard.md +0 -0
  87. {cmd2-4.1.2 → cmd2-4.2.1}/docs/features/commands.md +0 -0
  88. {cmd2-4.1.2 → cmd2-4.2.1}/docs/features/completion.md +0 -0
  89. {cmd2-4.1.2 → cmd2-4.2.1}/docs/features/disable_commands.md +0 -0
  90. {cmd2-4.1.2 → cmd2-4.2.1}/docs/features/embedded_python_shells.md +0 -0
  91. {cmd2-4.1.2 → cmd2-4.2.1}/docs/features/generating_output.md +0 -0
  92. {cmd2-4.1.2 → cmd2-4.2.1}/docs/features/help.md +0 -0
  93. {cmd2-4.1.2 → cmd2-4.2.1}/docs/features/history.md +0 -0
  94. {cmd2-4.1.2 → cmd2-4.2.1}/docs/features/hooks.md +0 -0
  95. {cmd2-4.1.2 → cmd2-4.2.1}/docs/features/index.md +0 -0
  96. {cmd2-4.1.2 → cmd2-4.2.1}/docs/features/initialization.md +0 -0
  97. {cmd2-4.1.2 → cmd2-4.2.1}/docs/features/misc.md +0 -0
  98. {cmd2-4.1.2 → cmd2-4.2.1}/docs/features/modular_commands.md +0 -0
  99. {cmd2-4.1.2 → cmd2-4.2.1}/docs/features/multiline_commands.md +0 -0
  100. {cmd2-4.1.2 → cmd2-4.2.1}/docs/features/os.md +0 -0
  101. {cmd2-4.1.2 → cmd2-4.2.1}/docs/features/packaging.md +0 -0
  102. {cmd2-4.1.2 → cmd2-4.2.1}/docs/features/plugins.md +0 -0
  103. {cmd2-4.1.2 → cmd2-4.2.1}/docs/features/prompt.md +0 -0
  104. {cmd2-4.1.2 → cmd2-4.2.1}/docs/features/redirection.md +0 -0
  105. {cmd2-4.1.2 → cmd2-4.2.1}/docs/features/scripting.md +0 -0
  106. {cmd2-4.1.2 → cmd2-4.2.1}/docs/features/settings.md +0 -0
  107. {cmd2-4.1.2 → cmd2-4.2.1}/docs/features/shortcuts_aliases_macros.md +0 -0
  108. {cmd2-4.1.2 → cmd2-4.2.1}/docs/features/startup_commands.md +0 -0
  109. {cmd2-4.1.2 → cmd2-4.2.1}/docs/features/table_creation.md +0 -0
  110. {cmd2-4.1.2 → cmd2-4.2.1}/docs/features/theme.md +0 -0
  111. {cmd2-4.1.2 → cmd2-4.2.1}/docs/index.md +0 -0
  112. {cmd2-4.1.2 → cmd2-4.2.1}/docs/javascripts/readthedocs.js +0 -0
  113. {cmd2-4.1.2 → cmd2-4.2.1}/docs/migrating/incompatibilities.md +0 -0
  114. {cmd2-4.1.2 → cmd2-4.2.1}/docs/migrating/index.md +0 -0
  115. {cmd2-4.1.2 → cmd2-4.2.1}/docs/migrating/minimum.md +0 -0
  116. {cmd2-4.1.2 → cmd2-4.2.1}/docs/migrating/next_steps.md +0 -0
  117. {cmd2-4.1.2 → cmd2-4.2.1}/docs/migrating/why.md +0 -0
  118. {cmd2-4.1.2 → cmd2-4.2.1}/docs/mixins/index.md +0 -0
  119. {cmd2-4.1.2 → cmd2-4.2.1}/docs/mixins/mixin_template.md +0 -0
  120. {cmd2-4.1.2 → cmd2-4.2.1}/docs/overrides/main.html +0 -0
  121. {cmd2-4.1.2 → cmd2-4.2.1}/docs/overview/alternatives.md +0 -0
  122. {cmd2-4.1.2 → cmd2-4.2.1}/docs/overview/index.md +0 -0
  123. {cmd2-4.1.2 → cmd2-4.2.1}/docs/overview/installation.md +0 -0
  124. {cmd2-4.1.2 → cmd2-4.2.1}/docs/overview/integrating.md +0 -0
  125. {cmd2-4.1.2 → cmd2-4.2.1}/docs/overview/resources.md +0 -0
  126. {cmd2-4.1.2 → cmd2-4.2.1}/docs/stylesheets/cmd2.css +0 -0
  127. {cmd2-4.1.2 → cmd2-4.2.1}/docs/testing.md +0 -0
  128. {cmd2-4.1.2 → cmd2-4.2.1}/examples/.cmd2rc +0 -0
  129. {cmd2-4.1.2 → cmd2-4.2.1}/examples/README.md +0 -0
  130. {cmd2-4.1.2 → cmd2-4.2.1}/examples/argparse_completion.py +0 -0
  131. {cmd2-4.1.2 → cmd2-4.2.1}/examples/argparse_example.py +0 -0
  132. {cmd2-4.1.2 → cmd2-4.2.1}/examples/async_call.py +0 -0
  133. {cmd2-4.1.2 → cmd2-4.2.1}/examples/async_commands.py +0 -0
  134. {cmd2-4.1.2 → cmd2-4.2.1}/examples/async_printing.py +0 -0
  135. {cmd2-4.1.2 → cmd2-4.2.1}/examples/basic_completion.py +0 -0
  136. {cmd2-4.1.2 → cmd2-4.2.1}/examples/cmd_as_argument.py +0 -0
  137. {cmd2-4.1.2 → cmd2-4.2.1}/examples/color.py +0 -0
  138. {cmd2-4.1.2 → cmd2-4.2.1}/examples/command_sets.py +0 -0
  139. {cmd2-4.1.2 → cmd2-4.2.1}/examples/completion_item_choices.py +0 -0
  140. {cmd2-4.1.2 → cmd2-4.2.1}/examples/custom_parser.py +0 -0
  141. {cmd2-4.1.2 → cmd2-4.2.1}/examples/custom_types.py +0 -0
  142. {cmd2-4.1.2 → cmd2-4.2.1}/examples/default_categories.py +0 -0
  143. {cmd2-4.1.2 → cmd2-4.2.1}/examples/dynamic_commands.py +0 -0
  144. {cmd2-4.1.2 → cmd2-4.2.1}/examples/environment.py +0 -0
  145. {cmd2-4.1.2 → cmd2-4.2.1}/examples/event_loops.py +0 -0
  146. {cmd2-4.1.2 → cmd2-4.2.1}/examples/exit_code.py +0 -0
  147. {cmd2-4.1.2 → cmd2-4.2.1}/examples/getting_started.py +0 -0
  148. {cmd2-4.1.2 → cmd2-4.2.1}/examples/hello_cmd2.py +0 -0
  149. {cmd2-4.1.2 → cmd2-4.2.1}/examples/help_categories.py +0 -0
  150. {cmd2-4.1.2 → cmd2-4.2.1}/examples/hooks.py +0 -0
  151. {cmd2-4.1.2 → cmd2-4.2.1}/examples/migrating.py +0 -0
  152. {cmd2-4.1.2 → cmd2-4.2.1}/examples/mixin.py +0 -0
  153. {cmd2-4.1.2 → cmd2-4.2.1}/examples/modular_commands/__init__.py +0 -0
  154. {cmd2-4.1.2 → cmd2-4.2.1}/examples/modular_commands/commandset_basic.py +0 -0
  155. {cmd2-4.1.2 → cmd2-4.2.1}/examples/modular_commands/commandset_complex.py +0 -0
  156. {cmd2-4.1.2 → cmd2-4.2.1}/examples/modular_commands/commandset_custominit.py +0 -0
  157. {cmd2-4.1.2 → cmd2-4.2.1}/examples/paged_output.py +0 -0
  158. {cmd2-4.1.2 → cmd2-4.2.1}/examples/persistent_history.py +0 -0
  159. {cmd2-4.1.2 → cmd2-4.2.1}/examples/pretty_print.py +0 -0
  160. {cmd2-4.1.2 → cmd2-4.2.1}/examples/python_scripting.py +0 -0
  161. {cmd2-4.1.2 → cmd2-4.2.1}/examples/read_input.py +0 -0
  162. {cmd2-4.1.2 → cmd2-4.2.1}/examples/remove_builtin_commands.py +0 -0
  163. {cmd2-4.1.2 → cmd2-4.2.1}/examples/remove_settable.py +0 -0
  164. {cmd2-4.1.2 → cmd2-4.2.1}/examples/rich_tables.py +0 -0
  165. {cmd2-4.1.2 → cmd2-4.2.1}/examples/rich_theme.py +0 -0
  166. {cmd2-4.1.2 → cmd2-4.2.1}/examples/scripts/arg_printer.py +0 -0
  167. {cmd2-4.1.2 → cmd2-4.2.1}/examples/scripts/conditional.py +0 -0
  168. {cmd2-4.1.2 → cmd2-4.2.1}/examples/scripts/nested.txt +0 -0
  169. {cmd2-4.1.2 → cmd2-4.2.1}/examples/scripts/quit.txt +0 -0
  170. {cmd2-4.1.2 → cmd2-4.2.1}/examples/scripts/save_help_text.py +0 -0
  171. {cmd2-4.1.2 → cmd2-4.2.1}/examples/scripts/script.py +0 -0
  172. {cmd2-4.1.2 → cmd2-4.2.1}/examples/scripts/script.txt +0 -0
  173. {cmd2-4.1.2 → cmd2-4.2.1}/examples/tmux_launch.sh +0 -0
  174. {cmd2-4.1.2 → cmd2-4.2.1}/examples/tmux_split.sh +0 -0
  175. {cmd2-4.1.2 → cmd2-4.2.1}/examples/unicode_commands.py +0 -0
  176. {cmd2-4.1.2 → cmd2-4.2.1}/mkdocs.yml +0 -0
  177. {cmd2-4.1.2 → cmd2-4.2.1}/package.json +0 -0
  178. {cmd2-4.1.2 → cmd2-4.2.1}/scripts/validate_tag.py +0 -0
  179. {cmd2-4.1.2 → cmd2-4.2.1}/setup.cfg +0 -0
  180. {cmd2-4.1.2 → cmd2-4.2.1}/tests/.cmd2rc +0 -0
  181. {cmd2-4.1.2 → cmd2-4.2.1}/tests/__init__.py +0 -0
  182. {cmd2-4.1.2 → cmd2-4.2.1}/tests/conftest.py +0 -0
  183. {cmd2-4.1.2 → cmd2-4.2.1}/tests/pyscript/echo.py +0 -0
  184. {cmd2-4.1.2 → cmd2-4.2.1}/tests/pyscript/environment.py +0 -0
  185. {cmd2-4.1.2 → cmd2-4.2.1}/tests/pyscript/help.py +0 -0
  186. {cmd2-4.1.2 → cmd2-4.2.1}/tests/pyscript/py_locals.py +0 -0
  187. {cmd2-4.1.2 → cmd2-4.2.1}/tests/pyscript/pyscript_dir.py +0 -0
  188. {cmd2-4.1.2 → cmd2-4.2.1}/tests/pyscript/raises_exception.py +0 -0
  189. {cmd2-4.1.2 → cmd2-4.2.1}/tests/pyscript/recursive.py +0 -0
  190. {cmd2-4.1.2 → cmd2-4.2.1}/tests/pyscript/self_in_py.py +0 -0
  191. {cmd2-4.1.2 → cmd2-4.2.1}/tests/pyscript/stop.py +0 -0
  192. {cmd2-4.1.2 → cmd2-4.2.1}/tests/pyscript/test_print.py +0 -0
  193. {cmd2-4.1.2 → cmd2-4.2.1}/tests/relative_multiple.txt +0 -0
  194. {cmd2-4.1.2 → cmd2-4.2.1}/tests/script.py +0 -0
  195. {cmd2-4.1.2 → cmd2-4.2.1}/tests/script.txt +0 -0
  196. {cmd2-4.1.2 → cmd2-4.2.1}/tests/scripts/binary.bin +0 -0
  197. {cmd2-4.1.2 → cmd2-4.2.1}/tests/scripts/empty.txt +0 -0
  198. {cmd2-4.1.2 → cmd2-4.2.1}/tests/scripts/help.txt +0 -0
  199. {cmd2-4.1.2 → cmd2-4.2.1}/tests/scripts/nested.txt +0 -0
  200. {cmd2-4.1.2 → cmd2-4.2.1}/tests/scripts/one_down.txt +0 -0
  201. {cmd2-4.1.2 → cmd2-4.2.1}/tests/scripts/postcmds.txt +0 -0
  202. {cmd2-4.1.2 → cmd2-4.2.1}/tests/scripts/precmds.txt +0 -0
  203. {cmd2-4.1.2 → cmd2-4.2.1}/tests/scripts/utf8.txt +0 -0
  204. {cmd2-4.1.2 → cmd2-4.2.1}/tests/test_argparse.py +0 -0
  205. {cmd2-4.1.2 → cmd2-4.2.1}/tests/test_argparse_subcommands.py +0 -0
  206. {cmd2-4.1.2 → cmd2-4.2.1}/tests/test_argparse_utils.py +0 -0
  207. {cmd2-4.1.2 → cmd2-4.2.1}/tests/test_categories.py +0 -0
  208. {cmd2-4.1.2 → cmd2-4.2.1}/tests/test_commandset.py +0 -0
  209. {cmd2-4.1.2 → cmd2-4.2.1}/tests/test_completion.py +0 -0
  210. {cmd2-4.1.2 → cmd2-4.2.1}/tests/test_dynamic_complete_style.py +0 -0
  211. {cmd2-4.1.2 → cmd2-4.2.1}/tests/test_future_annotations.py +0 -0
  212. {cmd2-4.1.2 → cmd2-4.2.1}/tests/test_history.py +0 -0
  213. {cmd2-4.1.2 → cmd2-4.2.1}/tests/test_parsing.py +0 -0
  214. {cmd2-4.1.2 → cmd2-4.2.1}/tests/test_plugin.py +0 -0
  215. {cmd2-4.1.2 → cmd2-4.2.1}/tests/test_pt_utils.py +0 -0
  216. {cmd2-4.1.2 → cmd2-4.2.1}/tests/test_py_completion.py +0 -0
  217. {cmd2-4.1.2 → cmd2-4.2.1}/tests/test_rich_utils.py +0 -0
  218. {cmd2-4.1.2 → cmd2-4.2.1}/tests/test_run_pyscript.py +0 -0
  219. {cmd2-4.1.2 → cmd2-4.2.1}/tests/test_string_utils.py +0 -0
  220. {cmd2-4.1.2 → cmd2-4.2.1}/tests/test_theme.py +0 -0
  221. {cmd2-4.1.2 → cmd2-4.2.1}/tests/test_utils.py +0 -0
  222. {cmd2-4.1.2 → cmd2-4.2.1}/tests/test_utils_defining_class.py +0 -0
@@ -14,7 +14,7 @@ repos:
14
14
  - id: trailing-whitespace
15
15
 
16
16
  - repo: https://github.com/astral-sh/ruff-pre-commit
17
- rev: "v0.15.21"
17
+ rev: "v0.16.3"
18
18
  hooks:
19
19
  - id: ruff-format
20
20
  args: [--config=ruff.toml]
@@ -30,7 +30,7 @@ repos:
30
30
  - prettier-plugin-toml@2.0.6
31
31
 
32
32
  - repo: https://github.com/crate-ci/typos
33
- rev: v1.48.0
33
+ rev: v1.49.0
34
34
  hooks:
35
35
  - id: typos
36
36
  exclude: |
@@ -1,3 +1,41 @@
1
+ ## 4.2.1 (August 22, 2026)
2
+
3
+ - Enhancements
4
+ - Added bracketed paste support so multiple pasted commands execute sequentially and multiline
5
+ commands continue as expected.
6
+
7
+ - Bug Fixes
8
+ - Enabled Ctrl-Z suspension at the prompt
9
+
10
+ ## 4.2.0 (August 6, 2026)
11
+
12
+ - Enhancements
13
+ - `@with_annotated` argument groups can now contain an `ArgumentBlock`'s arguments. A `Group`
14
+ member names a command-line argument, and a block expands into one argument per field, so its
15
+ fields are named: `Group("host", "port")`.
16
+ - Breaking Changes
17
+ - A `Group` member now names an argument rather than a parameter. The two differ only for an
18
+ `ArgumentBlock` parameter, which is expanded away and has no argument of its own:
19
+ `Group("conn")` now raises `ValueError` pointing at the block's fields. It previously produced
20
+ an empty argument group in `groups=` and a `KeyError` in `mutually_exclusive_groups=`.
21
+ - Whether a `Group` member exists is now validated when the parser is built rather than at
22
+ decoration time, because a block's field names cannot be known without resolving its type
23
+ hint, and resolving hints eagerly would break forward-referenced annotations. A typo in a
24
+ member name still raises `ValueError`, but on first use of that command rather than at class
25
+ definition. The spec-shape rules (a member listed twice, a member in two groups,
26
+ `required=True` on a plain group, the mutex nesting rules) are unaffected and still hard-fail
27
+ at decoration time.
28
+ - Bug Fixes
29
+ - Fixed `@with_annotated(base_command=True)` not listing its subcommands under the positional
30
+ arguments section of the parent command's `--help`, unlike `argparse` and
31
+ `Cmd2ArgumentParser`. They were placed in an untitled section of their own instead. Passing
32
+ `subcommand_title` or `subcommand_description` still gives the subcommands a dedicated section
33
+ ([#1715](https://github.com/python-cmd2/cmd2/issues/1715)).
34
+ - Fix `@with_annotated` decorator so using `ArgumentBlock` works with groups
35
+ ([#1718](https://github.com/python-cmd2/cmd2/issues/1718)).
36
+ - Fixed bug where already sorted `choices_provider` results were being re-sorted
37
+ ([#1727](https://github.com/python-cmd2/cmd2/issues/1727)).
38
+
1
39
  ## 4.1.2 (July 16, 2026)
2
40
 
3
41
  - Enhancements
@@ -18,8 +18,8 @@ check: ## Run code quality tools.
18
18
  @uv lock --locked
19
19
  @echo "🚀 Auto-formatting/Linting code and documentation: Running prek"
20
20
  @uv run prek run -a
21
- @echo "🚀 Static type checking: Running mypy"
22
- @uv run mypy
21
+ @echo "🚀 Static type checking: Running ty"
22
+ @uv run ty check
23
23
 
24
24
  .PHONY: format
25
25
  format: ## Perform ruff formatting
@@ -31,7 +31,7 @@ lint: ## Perform ruff linting
31
31
 
32
32
  .PHONY: typecheck
33
33
  typecheck: ## Perform type checking
34
- @uv run mypy
34
+ @uv run ty check
35
35
 
36
36
  .PHONY: test
37
37
  test: ## Test the code with pytest.
@@ -76,7 +76,7 @@ publish: validate-tag build ## Publish a release to PyPI, uses token from ~/.pyp
76
76
  # Define variables for files/directories to clean
77
77
  BUILD_DIRS = build dist *.egg-info
78
78
  DOC_DIRS = build
79
- MYPY_DIRS = .mypy_cache dmypy.json dmypy.sock
79
+ TY_DIRS = .ty_cache .red_knot_cache
80
80
  TEST_DIRS = .cache .pytest_cache htmlcov
81
81
  TEST_FILES = .coverage coverage.xml
82
82
 
@@ -90,10 +90,10 @@ clean-docs: ## Clean documentation artifacts
90
90
  @echo "🚀 Removing documentation artifacts"
91
91
  @uv run python -c "import shutil; import os; [shutil.rmtree(d, ignore_errors=True) for d in '$(DOC_DIRS)'.split() if os.path.isdir(d)]"
92
92
 
93
- .PHONY: clean-mypy
94
- clean-mypy: ## Clean mypy artifacts
95
- @echo "🚀 Removing mypy artifacts"
96
- @uv run python -c "import shutil; import os; [shutil.rmtree(d, ignore_errors=True) for d in '$(MYPY_DIRS)'.split() if os.path.isdir(d)]"
93
+ .PHONY: clean-ty
94
+ clean-ty: ## Clean ty artifacts
95
+ @echo "🚀 Removing ty artifacts"
96
+ @uv run python -c "import shutil; import os; [shutil.rmtree(d, ignore_errors=True) for d in '$(TY_DIRS)'.split() if os.path.isdir(d)]"
97
97
 
98
98
  .PHONY: clean-pycache
99
99
  clean-pycache: ## Clean pycache artifacts
@@ -112,7 +112,7 @@ clean-test: ## Clean test artifacts
112
112
  @uv run python -c "from pathlib import Path; [Path(f).unlink(missing_ok=True) for f in '$(TEST_FILES)'.split()]"
113
113
 
114
114
  .PHONY: clean
115
- clean: clean-build clean-docs clean-mypy clean-pycache clean-ruff clean-test ## Clean all artifacts
115
+ clean: clean-build clean-docs clean-ty clean-pycache clean-ruff clean-test ## Clean all artifacts
116
116
  @echo "🚀 Cleaned all artifacts"
117
117
 
118
118
  .PHONY: help
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cmd2
3
- Version: 4.1.2
3
+ Version: 4.2.1
4
4
  Summary: cmd2 - quickly build feature-rich and user-friendly interactive command line applications in Python
5
5
  Author: cmd2 Contributors
6
6
  License-Expression: MIT
@@ -21,7 +21,7 @@ Classifier: Topic :: Software Development :: Libraries :: Python Modules
21
21
  Requires-Python: >=3.11
22
22
  Description-Content-Type: text/markdown
23
23
  License-File: LICENSE
24
- Requires-Dist: prompt-toolkit>=3.0.52
24
+ Requires-Dist: prompt-toolkit>=3.0.53
25
25
  Requires-Dist: pyperclip>=1.8.2
26
26
  Requires-Dist: rich>=15.0.0
27
27
  Requires-Dist: rich-argparse>=1.7.2
@@ -175,9 +175,11 @@ dataclass constructor at reconstruction time, so a ``default_factory`` yields a
175
175
  argument. A field whose type is itself a block is not expanded (no recursion) and raises the
176
176
  unsupported-type error. Because fields expand flat, every field name must be unique across the command's
177
177
  own parameters and every other block's fields -- a collision (which would put two argparse actions on one
178
- namespace dest) raises ``TypeError`` when the parser is built. Block fields cannot participate in
179
- ``groups=`` / ``mutually_exclusive_groups=`` (those reference the function's own parameter names, which are
180
- validated without resolving type hints). A block must be the *bare* annotation of a regular parameter:
178
+ namespace dest) raises ``TypeError`` when the parser is built. Block fields can participate in
179
+ ``groups=`` / ``mutually_exclusive_groups=``: a group names arguments, and because a block expands to one
180
+ argument per field, you name its *field* names. Since resolving those field names needs the block's
181
+ type hints, this membership is checked when the parser is built rather than at decoration time. A block
182
+ must be the *bare* annotation of a regular parameter:
181
183
  wrapping it in ``Annotated``/``Optional``/a union, or using it as ``*args`` / ``**kwargs``, raises
182
184
  ``TypeError`` (to use a dataclass as a single value instead, make it a plain ``@dataclass`` with an
183
185
  ``Argument(converter=...)``). An ``ArgumentBlock`` subclass that is not a ``@dataclass`` has no fields and
@@ -414,7 +416,7 @@ class _BaseArgMetadata:
414
416
  """
415
417
  reserved = self._RESERVED_EXTRA_KWARGS & extra_kwargs.keys()
416
418
  if reserved:
417
- name = sorted(reserved)[0]
419
+ name = min(reserved)
418
420
  # Per-key remediation hint for the reserved kwarg.
419
421
  hint = {
420
422
  "type": (
@@ -552,25 +554,20 @@ class Group:
552
554
  ) -> None:
553
555
  """Initialise an argument group definition.
554
556
 
555
- :param members: parameter names to place in the group (at least one)
557
+ :param members: argument names to place in the group (at least one). A plain parameter's name is its
558
+ argument name; for an ``ArgumentBlock`` name its fields.
556
559
  :param title: group title shown as a help section header
557
560
  :param description: group description shown under the title
558
561
  :param required: ``mutually_exclusive_groups`` only -- require exactly one member.
559
562
  On a ``groups=`` entry it raises ``ValueError``.
560
563
  """
561
564
  if not members:
562
- raise ValueError("Group requires at least one member parameter name")
565
+ raise ValueError("Group requires at least one member argument name")
563
566
  self.members = members
564
567
  self.title = title
565
568
  self.description = description
566
569
  self.required = required
567
570
 
568
- def _validate_members(self, *, all_param_names: set[str], group_type: str) -> None:
569
- """Validate that every referenced member parameter exists."""
570
- for name in self.members:
571
- if name not in all_param_names:
572
- raise ValueError(f"{group_type} references nonexistent parameter {name!r}")
573
-
574
571
 
575
572
  #: Metadata extracted from ``Annotated[T, meta]``, or ``None`` for plain types.
576
573
  ArgMetadata = Argument | Option | None
@@ -711,10 +708,10 @@ class _CollectionCastingAction(argparse._StoreAction):
711
708
 
712
709
  def __call__(
713
710
  self,
714
- _parser: argparse.ArgumentParser,
711
+ parser: argparse.ArgumentParser, # noqa: ARG002
715
712
  namespace: argparse.Namespace,
716
713
  values: Any,
717
- _option_string: str | None = None,
714
+ option_string: str | None = None, # noqa: ARG002
718
715
  ) -> None:
719
716
  result = values
720
717
  if self._container_factory is not None and isinstance(values, list):
@@ -882,7 +879,7 @@ def _resolve_union(
882
879
  raise TypeError(f"Union type {type_names} is ambiguous for auto-resolution.")
883
880
 
884
881
  parts = [_resolve_base_type(member, allow_unknown_entry=allow_unknown_entry) for member in non_none]
885
- # Every part is an Enum (guarded above), so each has a converter; the None-filter keeps mypy happy.
882
+ # Every part is an Enum (guarded above), so each has a converter; the None-filter keeps the type checker happy.
886
883
  converters = [part.converter for part in parts if part.converter is not None]
887
884
  choices = _dedupe_choices(choice for part in parts for choice in (part.choices or []))
888
885
 
@@ -2072,6 +2069,39 @@ def _shared_field_dest(dc_type: type, field_name: str) -> str:
2072
2069
  return constants.cmd2_private_attr_name(f"shared_{id(dc_type):x}_{field_name}")
2073
2070
 
2074
2071
 
2072
+ def _check_group_members_exist(
2073
+ by_name: dict[str, _ArgparseArgument],
2074
+ block_param_names: set[str],
2075
+ func_qualname: str,
2076
+ *specs: tuple[Group, ...] | None,
2077
+ ) -> None:
2078
+ """Reject a ``Group`` member that names no command-line argument, once every argument is built.
2079
+
2080
+ :func:`_validate_group_specs` cannot do this at decoration time: an ArgumentBlock's arguments are its
2081
+ dataclass fields, which only resolving the block's type hint would reveal, and resolving hints there
2082
+ would break forward-referenced annotations. So this is where a bad member name is finally caught.
2083
+
2084
+ Naming the block parameter itself is the likely mistake -- it reads natural but the parameter is expanded
2085
+ away and has no argument -- so it gets its own message pointing at the fields to name instead.
2086
+ """
2087
+ for spec_group in specs:
2088
+ for spec in spec_group or ():
2089
+ for name in spec.members:
2090
+ if name in by_name:
2091
+ continue
2092
+ if name in block_param_names:
2093
+ raise ValueError(
2094
+ f"group in {func_qualname} references {name!r}, which is an ArgumentBlock parameter "
2095
+ f"and not a command-line argument: a block is expanded into one argument per field, "
2096
+ f"so name those fields instead."
2097
+ )
2098
+ raise ValueError(
2099
+ f"group in {func_qualname} references {name!r}, which is not a command-line argument. "
2100
+ f"Members name arguments: a plain parameter is its own argument, and an ArgumentBlock's "
2101
+ f"arguments are its field names."
2102
+ )
2103
+
2104
+
2075
2105
  def _link_mutex_group_membership(
2076
2106
  by_name: dict[str, _ArgparseArgument],
2077
2107
  mutually_exclusive_groups: tuple[Group, ...] | None,
@@ -2079,8 +2109,7 @@ def _link_mutex_group_membership(
2079
2109
  """Append each mutex group's 1-based index to its member arguments' ``mutex_group_indices``.
2080
2110
 
2081
2111
  This membership is the fact behind the required-member constraint row. Member references are
2082
- validated upstream by :func:`_validate_group_specs` before this runs, so every member name
2083
- resolves to a built argument.
2112
+ resolved to built arguments upstream by :func:`_check_group_members_exist` before this runs.
2084
2113
  """
2085
2114
  if not mutually_exclusive_groups:
2086
2115
  return
@@ -2160,7 +2189,7 @@ def _find_argument_block(hint: Any) -> type[ArgumentBlock] | None:
2160
2189
  return None
2161
2190
 
2162
2191
 
2163
- def _init_field_names(dc_type: type) -> list[str]:
2192
+ def _init_field_names(dc_type: Any) -> list[str]:
2164
2193
  """Names of a dataclass's ``init`` fields in definition order (the flat argument names of a block)."""
2165
2194
  return [f.name for f in fields(dc_type) if f.init]
2166
2195
 
@@ -2352,6 +2381,7 @@ def _resolve_parameters(
2352
2381
  *,
2353
2382
  skip_params: frozenset[str] = _SKIP_PARAMS,
2354
2383
  base_command: bool = False,
2384
+ groups: tuple[Group, ...] | None = None,
2355
2385
  mutually_exclusive_groups: tuple[Group, ...] | None = None,
2356
2386
  ) -> tuple[list[_ArgparseArgument], set[type]]:
2357
2387
  """Resolve a function signature into argparse-argument builders and inheritable-block types.
@@ -2378,6 +2408,7 @@ def _resolve_parameters(
2378
2408
  resolved: list[_ArgparseArgument] = []
2379
2409
  inherited_field_names: set[str] = set()
2380
2410
  base_args_types: set[type] = set()
2411
+ block_param_names: set[str] = set()
2381
2412
 
2382
2413
  # Skip the first parameter by position (self/cls for methods)
2383
2414
  params = list(sig.parameters.items())
@@ -2398,6 +2429,7 @@ def _resolve_parameters(
2398
2429
  f"cannot have a default value; remove the default."
2399
2430
  )
2400
2431
  inherited_field_names.update(_init_field_names(inherited))
2432
+ block_param_names.add(name)
2401
2433
  continue
2402
2434
 
2403
2435
  # An ArgumentBlock-typed parameter is an argument block: expand its fields in place (flat) instead of
@@ -2416,6 +2448,7 @@ def _resolve_parameters(
2416
2448
  block_hint, func_qualname=func.__qualname__, base_command=base_command, shared=name == NS_ATTR_BASE_ARGS
2417
2449
  )
2418
2450
  _reject_field_shadowing_block_param(block_hint, name, func.__qualname__)
2451
+ block_param_names.add(name)
2419
2452
  resolved.extend(expanded)
2420
2453
  continue
2421
2454
  # The magic name requires a bare ArgumentBlock; a non-block annotation on it is a clear mistake.
@@ -2480,6 +2513,7 @@ def _resolve_parameters(
2480
2513
  for arg in positionals[:-1]: # every positional except the last has a following positional
2481
2514
  arg.has_following_positional = True
2482
2515
  by_name = {arg.name: arg for arg in resolved}
2516
+ _check_group_members_exist(by_name, block_param_names, func.__qualname__, groups, mutually_exclusive_groups)
2483
2517
  _link_mutex_group_membership(by_name, mutually_exclusive_groups)
2484
2518
  for arg in resolved:
2485
2519
  arg._check_constraints()
@@ -2539,30 +2573,22 @@ def _filtered_namespace_kwargs(
2539
2573
 
2540
2574
 
2541
2575
  def _validate_group_specs(
2542
- func: Callable[..., Any],
2543
- *,
2544
- skip_params: frozenset[str],
2545
2576
  groups: tuple[Group, ...] | None,
2546
2577
  mutually_exclusive_groups: tuple[Group, ...] | None,
2547
2578
  ) -> None:
2548
- """Validate ``groups=`` / ``mutually_exclusive_groups=`` specs from parameter names alone.
2549
-
2550
- Runs at decoration time (from both the regular-command and subcommand decoration paths, and
2551
- again from :func:`build_parser_from_function` for direct callers), so a misconfigured group
2552
- hard-fails when the class is defined instead of on first command use, where cmd2's runtime
2553
- handler turns the error into a printed message. Reads only parameter names and the ``Group``
2554
- specs -- never the type hints -- so forward-referenced annotations still decorate. The one
2555
- group rule that needs the annotations (a required member in a mutually exclusive group) stays
2556
- in :data:`_CONSTRAINTS` and fires when the parser is built.
2579
+ """Validate the *shape* of ``groups=`` / ``mutually_exclusive_groups=`` specs.
2580
+
2581
+ Runs at decoration time (from both the regular-command and subcommand decoration paths, and again from
2582
+ :func:`build_parser_from_function` for direct callers), so a misshapen group hard-fails when the class is
2583
+ defined instead of on first command use, where cmd2's runtime handler turns the error into a printed
2584
+ message. Reads only the ``Group`` specs -- never the signature or the type hints -- so
2585
+ forward-referenced annotations still decorate.
2557
2586
  """
2558
2587
  if not groups and not mutually_exclusive_groups:
2559
2588
  return
2560
- params = list(inspect.signature(func).parameters)[1:] # skip self/cls by position
2561
- all_param_names = {name for name in params if name not in skip_params}
2562
2589
 
2563
2590
  group_entry_for: dict[str, int] = {}
2564
2591
  for index, spec in enumerate(groups or (), start=1):
2565
- spec._validate_members(all_param_names=all_param_names, group_type="groups")
2566
2592
  if spec.required:
2567
2593
  raise ValueError(
2568
2594
  "Group(required=True) is only valid in mutually_exclusive_groups; "
@@ -2580,7 +2606,6 @@ def _validate_group_specs(
2580
2606
 
2581
2607
  mutex_entry_for: dict[str, int] = {}
2582
2608
  for index, spec in enumerate(mutually_exclusive_groups or (), start=1):
2583
- spec._validate_members(all_param_names=all_param_names, group_type="mutually_exclusive_groups")
2584
2609
  for name in spec.members:
2585
2610
  previous = mutex_entry_for.get(name)
2586
2611
  if previous == index:
@@ -2721,7 +2746,7 @@ def build_parser_from_function(
2721
2746
  from . import argparse_utils
2722
2747
 
2723
2748
  # The decorator already ran this at decoration time; direct callers get the same checks here.
2724
- _validate_group_specs(func, skip_params=skip_params, groups=groups, mutually_exclusive_groups=mutually_exclusive_groups)
2749
+ _validate_group_specs(groups, mutually_exclusive_groups)
2725
2750
 
2726
2751
  parser_cls = parser_class or argparse_utils.DEFAULT_ARGUMENT_PARSER
2727
2752
  if "description" not in parser_kwargs:
@@ -2735,6 +2760,7 @@ def build_parser_from_function(
2735
2760
  resolved, base_args_types = _resolve_parameters(
2736
2761
  func,
2737
2762
  skip_params=skip_params,
2763
+ groups=groups,
2738
2764
  mutually_exclusive_groups=mutually_exclusive_groups,
2739
2765
  )
2740
2766
 
@@ -2827,15 +2853,17 @@ def _make_parser_builder(
2827
2853
  **options.parser_kwargs,
2828
2854
  )
2829
2855
  if base_command:
2830
- # dict[str, Any] is load-bearing: the typeshed stub types title/metavar as non-None,
2831
- # but argparse accepts None at runtime, so splatting avoids a false overload error.
2856
+ # dict[str, Any] is load-bearing: title/description are added conditionally below,
2857
+ # which the typeshed overloads for add_subparsers cannot express.
2832
2858
  kwargs: dict[str, Any] = {
2833
2859
  "dest": "subcommand",
2834
2860
  "metavar": options.subcommand_metavar,
2835
2861
  "required": options.subcommand_required,
2836
- "title": options.subcommand_title,
2837
- "description": options.subcommand_description,
2838
2862
  }
2863
+ if options.subcommand_title is not None:
2864
+ kwargs["title"] = options.subcommand_title
2865
+ if options.subcommand_description is not None:
2866
+ kwargs["description"] = options.subcommand_description
2839
2867
  parser.add_subparsers(**kwargs)
2840
2868
  return parser
2841
2869
 
@@ -2862,12 +2890,7 @@ def _build_subcommand_handler(
2862
2890
  # Validate the group specs eagerly (decoration time) so a misconfigured group hard-fails when
2863
2891
  # the class is defined; the name-only checks never resolve type hints, so forward-referenced
2864
2892
  # annotations still decorate and the parser build stays deferred.
2865
- _validate_group_specs(
2866
- func,
2867
- skip_params=_SKIP_PARAMS,
2868
- groups=options.groups,
2869
- mutually_exclusive_groups=options.mutually_exclusive_groups,
2870
- )
2893
+ _validate_group_specs(options.groups, options.mutually_exclusive_groups)
2871
2894
  if base_command:
2872
2895
  # Validate eagerly (decoration time); the base-command rows in _CONSTRAINTS fire here.
2873
2896
  # skip_params is spelled out so this call cannot silently diverge from the parser build below.
@@ -3051,12 +3074,7 @@ def with_annotated(
3051
3074
  # Validate the group specs eagerly (decoration time) so a misconfigured group hard-fails when
3052
3075
  # the class is defined; the name-only checks never resolve type hints, so forward-referenced
3053
3076
  # annotations still decorate and the parser build stays deferred.
3054
- _validate_group_specs(
3055
- fn,
3056
- skip_params=skip_params,
3057
- groups=options.groups,
3058
- mutually_exclusive_groups=options.mutually_exclusive_groups,
3059
- )
3077
+ _validate_group_specs(options.groups, options.mutually_exclusive_groups)
3060
3078
  if base_command:
3061
3079
  # Validate eagerly (decoration time); the base-command rows in _CONSTRAINTS fire here.
3062
3080
  _resolve_parameters(fn, skip_params=skip_params, base_command=True)
@@ -3095,6 +3113,9 @@ def with_annotated(
3095
3113
  except SystemExit as exc:
3096
3114
  raise Cmd2ArgparseError from exc
3097
3115
 
3116
+ if ns is None:
3117
+ raise ValueError("ns is None")
3118
+
3098
3119
  setattr(ns, constants.NS_ATTR_STATEMENT, statement)
3099
3120
  handler = getattr(ns, constants.NS_ATTR_SUBCOMMAND_FUNC, None)
3100
3121
  if base_command and handler is not None:
@@ -694,7 +694,7 @@ class ArgparseCompleter:
694
694
  self._parser.print_help(file)
695
695
 
696
696
  def _choices_to_items(self, arg_state: _ArgumentState) -> list[CompletionItem]:
697
- """Convert choices from action to list of CompletionItems."""
697
+ """Convert an action's choices to a list of CompletionItems."""
698
698
  if arg_state.action.choices is None:
699
699
  return []
700
700
 
@@ -785,13 +785,15 @@ class ArgparseCompleter:
785
785
  cmd_set,
786
786
  )
787
787
  all_choices = list(choices_provider(*args, **kwargs))
788
+ sort = False # choices_provider results are already sorted, so don't re-sort them
788
789
  else:
789
790
  all_choices = self._choices_to_items(arg_state)
791
+ sort = True
790
792
 
791
793
  # Filter used values and run basic completion
792
794
  used_values = consumed_arg_values.get(arg_state.action.dest, [])
793
795
  filtered = [choice for choice in all_choices if choice.text not in used_values]
794
- completions = self._cmd_app.basic_complete(text, line, begidx, endidx, filtered)
796
+ completions = self._cmd_app.basic_complete(text, line, begidx, endidx, filtered, sort=sort)
795
797
 
796
798
  return self._build_completion_table(arg_state, completions)
797
799
 
@@ -747,8 +747,8 @@ class Cmd2ArgumentParser(argparse.ArgumentParser):
747
747
  super().__init__(
748
748
  prog=prog,
749
749
  usage=usage,
750
- description=description, # type: ignore[arg-type]
751
- epilog=epilog, # type: ignore[arg-type]
750
+ description=description, # type: ignore[arg-type, ty:invalid-argument-type]
751
+ epilog=epilog, # type: ignore[arg-type, ty:invalid-argument-type]
752
752
  parents=parents,
753
753
  formatter_class=formatter_class,
754
754
  prefix_chars=prefix_chars,
@@ -772,7 +772,7 @@ class Cmd2ArgumentParser(argparse.ArgumentParser):
772
772
  self.description: HelpContent | None # type: ignore[assignment]
773
773
  self.epilog: HelpContent | None # type: ignore[assignment]
774
774
 
775
- def print_usage(self, file: IO[str] | None = None) -> None: # type:ignore[override]
775
+ def print_usage(self, file: IO[str] | None = None) -> None: # type: ignore[override, ty:invalid-method-override]
776
776
  """Override to ensure the formatter is aware of the target file."""
777
777
  if file is None:
778
778
  file = self._thread_locals.current_output_file
@@ -780,7 +780,7 @@ class Cmd2ArgumentParser(argparse.ArgumentParser):
780
780
  with self.output_to(file):
781
781
  super().print_usage(file)
782
782
 
783
- def print_help(self, file: IO[str] | None = None) -> None: # type:ignore[override]
783
+ def print_help(self, file: IO[str] | None = None) -> None: # type: ignore[override, ty:invalid-method-override]
784
784
  """Override to ensure the formatter is aware of the target file."""
785
785
  if file is None:
786
786
  file = self._thread_locals.current_output_file
@@ -831,7 +831,7 @@ class Cmd2ArgumentParser(argparse.ArgumentParser):
831
831
  temp_parser = Cmd2ArgumentParser(
832
832
  prog=self.prog,
833
833
  usage=None,
834
- formatter_class=self.formatter_class,
834
+ formatter_class=cast(type[Cmd2HelpFormatter], self.formatter_class),
835
835
  add_help=False,
836
836
  )
837
837
 
@@ -1037,7 +1037,8 @@ class Cmd2ArgumentParser(argparse.ArgumentParser):
1037
1037
 
1038
1038
  def _get_formatter(self, *_args: Any, **_kwargs: Any) -> Cmd2HelpFormatter:
1039
1039
  """Override with customizations for Cmd2HelpFormatter."""
1040
- return self.formatter_class(prog=self.prog, file=self._thread_locals.current_output_file)
1040
+ formatter_class = cast(type[Cmd2HelpFormatter], self.formatter_class)
1041
+ return formatter_class(prog=self.prog, file=self._thread_locals.current_output_file)
1041
1042
 
1042
1043
  def format_help(self, *args: Any, **kwargs: Any) -> str:
1043
1044
  """Override to add a newline."""