From da33b074c842edb7091799cdc991cc52c8368420 Mon Sep 17 00:00:00 2001 From: traal <11281108+harkabeeparolus@users.noreply.github.com> Date: Fri, 2 Feb 2024 07:51:41 +0100 Subject: [PATCH 01/11] Update similar.rst Add link and description for Typer. --- docs/similar.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/similar.rst b/docs/similar.rst index 97fd7d6..6862067 100644 --- a/docs/similar.rst +++ b/docs/similar.rst @@ -50,6 +50,10 @@ supports Python3. Not every "yes" in this table would count as pro. questionable practice); it does not derive the CLI arguments from the function signature but entirely relies on additional decorators, while Argh strives for the opposite. +* typer_ is a wrapper on top of `click`, which works with type hints instead + of decorators. This is very similar to argh's new 2024 design. Typer also + adds a lot of bells and whistles, and optional color support with `rich`, + so it is a full-featured package with several dependencies. .. _argdeclare: http://code.activestate.com/recipes/576935-argdeclare-declarative-interface-to-argparse/ .. _argparse-cli: http://code.google.com/p/argparse-cli/ @@ -69,3 +73,4 @@ supports Python3. Not every "yes" in this table would count as pro. .. _cement: http://builtoncement.com/2.0/ .. _autocommand: https://pypi.python.org/pypi/autocommand/ .. _click: https://click.palletsprojects.com +.. _typer: https://typer.tiangolo.com From 0ff6c311d7595ddb9091ad7f2c13fdce9046df40 Mon Sep 17 00:00:00 2001 From: Andy Mikhaylenko Date: Sun, 16 Jun 2024 22:18:37 +0200 Subject: [PATCH 02/11] chore: bump version --- CHANGELOG.rst | 3 +++ pyproject.toml | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index ef2a548..8409d1c 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,6 +1,9 @@ Changelog ========= +Version 0.31.3 +-------------- + Version 0.31.2 (2024-01-24) --------------------------- diff --git a/pyproject.toml b/pyproject.toml index 90f9f80..bebad1f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "flit_core.buildapi" [project] name = "argh" -version = "0.31.2" +version = "0.31.3" description = "Plain Python functions as CLI commands without boilerplate" readme = "README.rst" requires-python = ">=3.8" From cdb70d5ac7d8a5a5dff3c55100e5f67b68b26826 Mon Sep 17 00:00:00 2001 From: Andy Mikhaylenko Date: Sun, 16 Jun 2024 22:38:21 +0200 Subject: [PATCH 03/11] fix: tests under Python 3.13 (fixes #228) --- docs/index.rst | 2 +- pyproject.toml | 1 + tests/test_integration.py | 97 +++++++++++++++++++++++++++++++-------- tox.ini | 2 + 4 files changed, 82 insertions(+), 20 deletions(-) diff --git a/docs/index.rst b/docs/index.rst index e4047b1..aeb04d0 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -4,7 +4,7 @@ Dependencies ------------ The `argh` library is supported (and tested unless otherwise specified) -on the following versions of Python: 3.8, 3.9, 3.10, 3.11, 3.12. +on the following versions of Python: 3.8, 3.9, 3.10, 3.11, 3.12, .13. If you need support for ancient Pythons, please use the following versions of Argh (the numeric puns were semi-intentional): diff --git a/pyproject.toml b/pyproject.toml index bebad1f..dd25a2f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -36,6 +36,7 @@ classifiers = [ "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", "Topic :: Software Development :: User Interfaces", diff --git a/tests/test_integration.py b/tests/test_integration.py index f863386..258a148 100644 --- a/tests/test_integration.py +++ b/tests/test_integration.py @@ -724,18 +724,34 @@ def remind( help_normalised = re.sub(r"\s+", " ", parser.format_help()) assert "name 'Basil'" in help_normalised - assert "-t TASK, --task TASK 'hang the Moose'" in help_normalised - assert ( - "-r REASON, --reason REASON 'there are creatures living in it'" - in help_normalised - ) - # explicit help message is not obscured by the implicit one - # but is still present - assert ( - "-n NOTE, --note NOTE why is it a remarkable animal? " - "(default: 'it can speak English')" - ) in help_normalised + # argh#228 — argparse in Python before 3.13 duplicated the placeholder in help + if sys.version_info < (3, 13): + assert "-t TASK, --task TASK 'hang the Moose'" in help_normalised + assert ( + "-r REASON, --reason REASON 'there are creatures living in it'" + in help_normalised + ) + + # explicit help message is not obscured by the implicit one + # but is still present + assert ( + "-n NOTE, --note NOTE why is it a remarkable animal? " + "(default: 'it can speak English')" + ) in help_normalised + else: + assert "-t, --task TASK 'hang the Moose'" in help_normalised + assert ( + "-r, --reason REASON 'there are creatures living in it'" + in help_normalised + ) + + # explicit help message is not obscured by the implicit one + # but is still present + assert ( + "-n, --note NOTE why is it a remarkable animal? " + "(default: 'it can speak English')" + ) in help_normalised def test_default_arg_values_in_help__regression(): @@ -750,9 +766,16 @@ def foo(*, bar=""): # doesn't break parser.format_help() + # argh#228 — argparse in Python before 3.13 duplicated the placeholder in help + if sys.version_info < (3, 13): + expected_line = "-b BAR, --bar BAR ''" + # note the empty str repr ^^^ + else: + expected_line = "-b, --bar BAR ''" + # note the empty str repr ^^^ + # now check details - assert "-b BAR, --bar BAR ''" in parser.format_help() - # note the empty str repr ^^^ + assert expected_line in parser.format_help() def test_help_formatting_is_preserved(): @@ -868,6 +891,19 @@ def second_func(): run(parser, "first-func --help", exit=True) captured = capsys.readouterr() + + # argh#228 — argparse in Python before 3.13 duplicated the placeholder in help + if sys.version_info < (3, 13): + arg_help_lines = ( + " -h, --help show this help message and exit\n" + " -f FOO, --foo FOO 123" + ) + else: + arg_help_lines = ( + " -h, --help show this help message and exit\n" + " -f, --foo FOO 123" + ) + assert ( captured.out == unindent( @@ -877,8 +913,7 @@ def second_func(): Owl stretching time {HELP_OPTIONS_LABEL}: - -h, --help show this help message and exit - -f FOO, --foo FOO 123 + {arg_help_lines} """ )[1:] ) @@ -997,6 +1032,19 @@ def second_func(): run(parser, "my-group first-func --help", exit=True) captured = capsys.readouterr() + + # argh#228 — argparse in Python before 3.13 duplicated the placeholder in help + if sys.version_info < (3, 13): + arg_help_lines = ( + " -h, --help show this help message and exit\n" + " -f FOO, --foo FOO 123" + ) + else: + arg_help_lines = ( + " -h, --help show this help message and exit\n" + " -f, --foo FOO 123" + ) + assert ( captured.out == unindent( @@ -1006,8 +1054,7 @@ def second_func(): Owl stretching time {HELP_OPTIONS_LABEL}: - -h, --help show this help message and exit - -f FOO, --foo FOO 123 + {arg_help_lines} """ )[1:] ) @@ -1079,6 +1126,19 @@ def second_func(): run(parser, "first-func --help", exit=True) captured = capsys.readouterr() + + # argh#228 — argparse in Python before 3.13 duplicated the placeholder in help + if sys.version_info < (3, 13): + arg_help_lines = ( + " -h, --help show this help message and exit\n" + " -f FOO, --foo FOO 123" + ) + else: + arg_help_lines = ( + " -h, --help show this help message and exit\n" + " -f, --foo FOO 123" + ) + assert ( captured.out == unindent( @@ -1088,8 +1148,7 @@ def second_func(): func description override {HELP_OPTIONS_LABEL}: - -h, --help show this help message and exit - -f FOO, --foo FOO 123 + {arg_help_lines} """ )[1:] ) diff --git a/tox.ini b/tox.ini index c3eb094..52615c0 100644 --- a/tox.ini +++ b/tox.ini @@ -5,6 +5,7 @@ envlist = py310 py311 py312 + py313 pypy3 as-module lint @@ -21,6 +22,7 @@ python = 3.10: py310 3.11: py311,lint,as-module 3.12: py312 + 3.13: py313 pypy-3.9: pypy3 pypy-3.10: pypy3 From 706e57d6c5d5a38ef92a2b61d83b94999d0e8fc0 Mon Sep 17 00:00:00 2001 From: Andy Mikhaylenko Date: Sun, 16 Jun 2024 22:40:21 +0200 Subject: [PATCH 04/11] docs: fix typo --- docs/index.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/index.rst b/docs/index.rst index aeb04d0..ffe657b 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -4,7 +4,7 @@ Dependencies ------------ The `argh` library is supported (and tested unless otherwise specified) -on the following versions of Python: 3.8, 3.9, 3.10, 3.11, 3.12, .13. +on the following versions of Python: 3.8, 3.9, 3.10, 3.11, 3.12, 3.13. If you need support for ancient Pythons, please use the following versions of Argh (the numeric puns were semi-intentional): From 3b3a3201253b2003a7a9ec2146e56b1d153fa562 Mon Sep 17 00:00:00 2001 From: Andy Mikhaylenko Date: Sun, 16 Jun 2024 22:45:33 +0200 Subject: [PATCH 05/11] =?UTF-8?q?lint:=20replace=20Black=20&=20C=C2=B0=20w?= =?UTF-8?q?ith=20Ruff?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .pre-commit-config.yaml | 19 ++++--------------- tests/test_integration.py | 12 ++++-------- 2 files changed, 8 insertions(+), 23 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 7cf4098..4e4c6bc 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -14,22 +14,11 @@ repos: - id: check-yaml files: .*\.(yaml|yml)$ - - repo: https://github.com/pycqa/isort - rev: 5.12.0 + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.4.9 hooks: - - id: isort - name: isort - - - repo: https://github.com/psf/black - rev: 23.9.1 - hooks: - - id: black - language_version: python3 - - - repo: https://github.com/PyCQA/flake8 - rev: 6.1.0 - hooks: - - id: flake8 + - id: ruff + - id: ruff-format - repo: https://github.com/pre-commit/mirrors-mypy rev: v1.5.1 diff --git a/tests/test_integration.py b/tests/test_integration.py index 258a148..7764160 100644 --- a/tests/test_integration.py +++ b/tests/test_integration.py @@ -742,8 +742,7 @@ def remind( else: assert "-t, --task TASK 'hang the Moose'" in help_normalised assert ( - "-r, --reason REASON 'there are creatures living in it'" - in help_normalised + "-r, --reason REASON 'there are creatures living in it'" in help_normalised ) # explicit help message is not obscured by the implicit one @@ -900,8 +899,7 @@ def second_func(): ) else: arg_help_lines = ( - " -h, --help show this help message and exit\n" - " -f, --foo FOO 123" + " -h, --help show this help message and exit\n" " -f, --foo FOO 123" ) assert ( @@ -1041,8 +1039,7 @@ def second_func(): ) else: arg_help_lines = ( - " -h, --help show this help message and exit\n" - " -f, --foo FOO 123" + " -h, --help show this help message and exit\n" " -f, --foo FOO 123" ) assert ( @@ -1135,8 +1132,7 @@ def second_func(): ) else: arg_help_lines = ( - " -h, --help show this help message and exit\n" - " -f, --foo FOO 123" + " -h, --help show this help message and exit\n" " -f, --foo FOO 123" ) assert ( From a5d556a778426a82cb8bb66150fe6c6d49ea9b6d Mon Sep 17 00:00:00 2001 From: Andy Mikhaylenko Date: Sun, 16 Jun 2024 22:57:29 +0200 Subject: [PATCH 06/11] style: apply `ruff format` --- docs/conf.py | 1 + pyproject.toml | 7 ------- src/argh/__init__.py | 1 + src/argh/assembling.py | 10 +++++----- src/argh/completion.py | 1 + src/argh/constants.py | 1 + src/argh/decorators.py | 1 + src/argh/dispatching.py | 1 + src/argh/dto.py | 1 + src/argh/helpers.py | 1 + src/argh/interaction.py | 1 + src/argh/utils.py | 16 ++++++---------- tests/base.py | 1 + tests/test_assembling.py | 16 ++++++---------- tests/test_completion.py | 1 + tests/test_decorators.py | 1 + tests/test_dispatching.py | 7 +++---- tests/test_dto.py | 13 +++++-------- tests/test_integration.py | 4 ++-- tests/test_interaction.py | 1 + tests/test_mapping_policies.py | 3 +-- tests/test_regressions.py | 4 ++-- tests/test_utils.py | 1 + 23 files changed, 44 insertions(+), 50 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index c54bf35..bb8c1ea 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,4 +1,5 @@ """Configuration file for the Sphinx documentation builder.""" + import os import sys from datetime import date diff --git a/pyproject.toml b/pyproject.toml index dd25a2f..83e4761 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -72,13 +72,6 @@ linters = [ [tool.distutils.bdist_wheel] universal = 1 -[tool.isort] -multi_line_output = 3 -profile = "black" - -[tool.black] -target-version = ["py38", "py39", "py310", "py311", "py312"] - [tool.bandit] exclude_dirs = ["tests"] diff --git a/src/argh/__init__.py b/src/argh/__init__.py index 5d6b048..875ec44 100644 --- a/src/argh/__init__.py +++ b/src/argh/__init__.py @@ -2,6 +2,7 @@ Argh ~~~~ """ + # # Copyright © 2010—2023 Andrey Mikhaylenko and contributors # diff --git a/src/argh/assembling.py b/src/argh/assembling.py index 7a690ea..fdfc4a0 100644 --- a/src/argh/assembling.py +++ b/src/argh/assembling.py @@ -13,6 +13,7 @@ Functions and classes to properly assemble your commands in a parser. """ + import inspect import textwrap import warnings @@ -517,9 +518,9 @@ def _merge_inferred_and_declared_args( # arguments inferred from function signature for parser_add_argument_spec in inferred_args: - specs_by_func_arg_name[ - parser_add_argument_spec.func_arg_name - ] = parser_add_argument_spec + specs_by_func_arg_name[parser_add_argument_spec.func_arg_name] = ( + parser_add_argument_spec + ) # arguments declared via @arg decorator for declared_spec in declared_args: @@ -734,8 +735,7 @@ def add_subcommands( add_commands(parser, functions, group_name=group_name, group_kwargs=group_kwargs) -class ArgumentNameMappingError(AssemblingError): - ... +class ArgumentNameMappingError(AssemblingError): ... class TypingHintArgSpecGuesser: diff --git a/src/argh/completion.py b/src/argh/completion.py index 93b5f37..f2069be 100644 --- a/src/argh/completion.py +++ b/src/argh/completion.py @@ -57,6 +57,7 @@ def func(...): ... """ + import logging import os from argparse import ArgumentParser diff --git a/src/argh/constants.py b/src/argh/constants.py index 9bf1672..07a21d8 100644 --- a/src/argh/constants.py +++ b/src/argh/constants.py @@ -11,6 +11,7 @@ Constants ~~~~~~~~~ """ + import argparse __all__ = ( diff --git a/src/argh/decorators.py b/src/argh/decorators.py index 8913a95..aaa20b7 100644 --- a/src/argh/decorators.py +++ b/src/argh/decorators.py @@ -11,6 +11,7 @@ Command decorators ~~~~~~~~~~~~~~~~~~ """ + from typing import Callable, List, Optional from argh.constants import ( diff --git a/src/argh/dispatching.py b/src/argh/dispatching.py index ef413f6..a5233d4 100644 --- a/src/argh/dispatching.py +++ b/src/argh/dispatching.py @@ -11,6 +11,7 @@ Dispatching ~~~~~~~~~~~ """ + import argparse import inspect import io diff --git a/src/argh/dto.py b/src/argh/dto.py index e4a32cd..eb2d1c6 100644 --- a/src/argh/dto.py +++ b/src/argh/dto.py @@ -1,6 +1,7 @@ """ Data transfer objects for internal usage. """ + from dataclasses import dataclass, field from typing import Any, Callable, Dict, List, Optional, Type, Union diff --git a/src/argh/helpers.py b/src/argh/helpers.py index 154ff7c..770fdca 100644 --- a/src/argh/helpers.py +++ b/src/argh/helpers.py @@ -11,6 +11,7 @@ Helpers ~~~~~~~ """ + import argparse from typing import Optional, Sequence diff --git a/src/argh/interaction.py b/src/argh/interaction.py index ab8f0e3..d06ce28 100644 --- a/src/argh/interaction.py +++ b/src/argh/interaction.py @@ -11,6 +11,7 @@ Interaction ~~~~~~~~~~~ """ + from typing import Optional __all__ = ["confirm"] diff --git a/src/argh/utils.py b/src/argh/utils.py index 3f7d42d..40aa40d 100644 --- a/src/argh/utils.py +++ b/src/argh/utils.py @@ -11,6 +11,7 @@ Utilities ~~~~~~~~~ """ + import argparse import re from typing import Tuple @@ -59,8 +60,7 @@ def unindent(text: str) -> str: return re.sub(rf"(^|\n) {{{depth}}}", "\\1", text) -class SubparsersNotDefinedError(Exception): - ... +class SubparsersNotDefinedError(Exception): ... def naive_guess_func_arg_name(option_strings: Tuple[str, ...]) -> str: @@ -89,17 +89,13 @@ def _opt_to_func_arg_name(opt: str) -> str: ) -class ArghError(Exception): - ... +class ArghError(Exception): ... -class CliArgToFuncArgGuessingError(ArghError): - ... +class CliArgToFuncArgGuessingError(ArghError): ... -class TooManyPositionalArgumentNames(CliArgToFuncArgGuessingError): - ... +class TooManyPositionalArgumentNames(CliArgToFuncArgGuessingError): ... -class MixedPositionalAndOptionalArgsError(CliArgToFuncArgGuessingError): - ... +class MixedPositionalAndOptionalArgsError(CliArgToFuncArgGuessingError): ... diff --git a/tests/base.py b/tests/base.py index d8e68c7..7710443 100644 --- a/tests/base.py +++ b/tests/base.py @@ -2,6 +2,7 @@ Common stuff for tests ~~~~~~~~~~~~~~~~~~~~~~ """ + import io import os import sys diff --git a/tests/test_assembling.py b/tests/test_assembling.py index 23c17a0..0b82eb5 100644 --- a/tests/test_assembling.py +++ b/tests/test_assembling.py @@ -2,6 +2,7 @@ Unit Tests For Assembling Phase ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ """ + import argparse from typing import Literal, Optional from unittest.mock import MagicMock, call, patch @@ -85,8 +86,7 @@ def test_guess_action_from_default(): def test_positional_with_default_int(): - def func(pos_int_default=123): - ... + def func(pos_int_default=123): ... parser = argh.ArghParser(prog="test") parser.set_default_command( @@ -97,8 +97,7 @@ def func(pos_int_default=123): def test_positional_with_default_bool(): - def func(pos_bool_default=False): - ... + def func(pos_bool_default=False): ... parser = argh.ArghParser(prog="test") parser.set_default_command( @@ -500,8 +499,7 @@ def func(): def test_set_default_command__varkwargs_sharing_prefix(): - def func(*, alpha: str = "Alpha", aleph: str = "Aleph"): - ... + def func(*, alpha: str = "Alpha", aleph: str = "Aleph"): ... parser = argh.ArghParser() parser.add_argument = MagicMock() @@ -767,11 +765,9 @@ def test_is_positional(): def test_typing_hints_only_used_when_arg_deco_not_used(): @argh.arg("foo", type=int) - def func_decorated(foo: Optional[float]): - ... + def func_decorated(foo: Optional[float]): ... - def func_undecorated(bar: Optional[float]): - ... + def func_undecorated(bar: Optional[float]): ... parser = argparse.ArgumentParser() parser.add_argument = MagicMock() diff --git a/tests/test_completion.py b/tests/test_completion.py index afb4b37..70548ba 100644 --- a/tests/test_completion.py +++ b/tests/test_completion.py @@ -3,6 +3,7 @@ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ """ + from unittest.mock import patch import argh diff --git a/tests/test_decorators.py b/tests/test_decorators.py index 7c9b7cb..9bf9e67 100644 --- a/tests/test_decorators.py +++ b/tests/test_decorators.py @@ -2,6 +2,7 @@ Unit Tests For Decorators ~~~~~~~~~~~~~~~~~~~~~~~~~ """ + import pytest import argh diff --git a/tests/test_dispatching.py b/tests/test_dispatching.py index 1bfaf72..d60556c 100644 --- a/tests/test_dispatching.py +++ b/tests/test_dispatching.py @@ -2,6 +2,7 @@ Dispatching tests ~~~~~~~~~~~~~~~~~ """ + import argparse import io from unittest.mock import Mock, patch @@ -198,8 +199,7 @@ def hit(): def test_dispatch_command_naming_policy( parser_cls_mock, set_default_command_mock, dispatch_mock ): - def func(): - ... + def func(): ... parser_mock = Mock() parser_cls_mock.return_value = parser_mock @@ -234,8 +234,7 @@ def func(): def test_dispatch_commands_naming_policy( parser_cls_mock, add_commands_mock, dispatch_mock ): - def func(): - ... + def func(): ... parser_mock = Mock() parser_cls_mock.return_value = parser_mock diff --git a/tests/test_dto.py b/tests/test_dto.py index b5e3c3a..86c4b06 100644 --- a/tests/test_dto.py +++ b/tests/test_dto.py @@ -2,12 +2,12 @@ Unit Tests For the Argument DTO ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ """ + from argh.dto import ParserAddArgumentSpec def test_update_empty_dto() -> None: - def stub_completer(): - ... + def stub_completer(): ... dto = ParserAddArgumentSpec( func_arg_name="foo", @@ -37,11 +37,9 @@ def stub_completer(): def test_update_full_dto() -> None: - def stub_completer_one(): - ... + def stub_completer_one(): ... - def stub_completer_two(): - ... + def stub_completer_two(): ... dto = ParserAddArgumentSpec( func_arg_name="foo", @@ -75,8 +73,7 @@ def stub_completer_two(): ) -class TestGetAllKwargs: - ... +class TestGetAllKwargs: ... def test_make_from_kwargs_minimal() -> None: diff --git a/tests/test_integration.py b/tests/test_integration.py index 7764160..8839265 100644 --- a/tests/test_integration.py +++ b/tests/test_integration.py @@ -2,6 +2,7 @@ Integration Tests ~~~~~~~~~~~~~~~~~ """ + import argparse import re import sys @@ -831,8 +832,7 @@ def cmd(*, foo=1): def test_add_commands_unknown_name_mapping_policy(): - def func(foo): - ... + def func(foo): ... parser = argh.ArghParser(prog="myapp") diff --git a/tests/test_interaction.py b/tests/test_interaction.py index f1dee89..5d903e6 100644 --- a/tests/test_interaction.py +++ b/tests/test_interaction.py @@ -2,6 +2,7 @@ Interaction Tests ~~~~~~~~~~~~~~~~~ """ + import unittest.mock as mock import argh diff --git a/tests/test_mapping_policies.py b/tests/test_mapping_policies.py index db67e71..e77e122 100644 --- a/tests/test_mapping_policies.py +++ b/tests/test_mapping_policies.py @@ -15,8 +15,7 @@ @pytest.mark.parametrize("name_mapping_policy", POLICIES) def test_no_args(name_mapping_policy) -> None: - def func() -> None: - ... + def func() -> None: ... parser = _make_parser_for_function(func, name_mapping_policy=name_mapping_policy) assert_usage(parser, "usage: test [-h]") diff --git a/tests/test_regressions.py b/tests/test_regressions.py index 562a042..343da81 100644 --- a/tests/test_regressions.py +++ b/tests/test_regressions.py @@ -2,6 +2,7 @@ Regression tests ~~~~~~~~~~~~~~~~ """ + import sys from typing import List, Optional, TextIO @@ -166,8 +167,7 @@ def test_regression_issue204(): We should avoid `deepcopy()` in standard operations. """ - def func(*, x: TextIO = sys.stdout) -> None: - ... + def func(*, x: TextIO = sys.stdout) -> None: ... parser = DebugArghParser() parser.set_default_command(func) diff --git a/tests/test_utils.py b/tests/test_utils.py index df91f47..2b9d17f 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -3,6 +3,7 @@ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ """ + from argparse import ArgumentParser, _SubParsersAction import pytest From b8af67197fcf003f30b638a2094fdca70c1db192 Mon Sep 17 00:00:00 2001 From: Andy Mikhaylenko Date: Sun, 16 Jun 2024 23:01:36 +0200 Subject: [PATCH 07/11] docs: mention latest fix --- CHANGELOG.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 8409d1c..02874c0 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -4,6 +4,10 @@ Changelog Version 0.31.3 -------------- +Bugs fixed: + +- test failures under Python 3.13 (issue #228). + Version 0.31.2 (2024-01-24) --------------------------- From 9ef9a6204294dc4e2a709ca251a07c0f2e59d37f Mon Sep 17 00:00:00 2001 From: laazy Date: Tue, 9 Jul 2024 15:01:46 +0800 Subject: [PATCH 08/11] Fix type annotation of `errors` in `wrap_errors` original typing is List[Exception], which is not the type of Exception class such as [AssertionError]. should be fixed as List[Type[Exception]]. --- src/argh/decorators.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/argh/decorators.py b/src/argh/decorators.py index 8913a95..abb778a 100644 --- a/src/argh/decorators.py +++ b/src/argh/decorators.py @@ -11,7 +11,7 @@ Command decorators ~~~~~~~~~~~~~~~~~~ """ -from typing import Callable, List, Optional +from typing import Callable, List, Optional, Type from argh.constants import ( ATTR_ALIASES, @@ -161,7 +161,7 @@ def wrapper(func: Callable) -> Callable: def wrap_errors( - errors: Optional[List[Exception]] = None, + errors: Optional[List[Type[Exception]]] = None, processor: Optional[Callable] = None, *args, ) -> Callable: From 7f7d6f4e665a18e9f70297a8b44910845e2d003b Mon Sep 17 00:00:00 2001 From: Andy Mikhaylenko Date: Sat, 13 Jul 2024 18:21:07 +0200 Subject: [PATCH 09/11] docs: update changelog --- CHANGELOG.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 02874c0..77c9fcd 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -6,7 +6,8 @@ Version 0.31.3 Bugs fixed: -- test failures under Python 3.13 (issue #228). +- fix type annotation of `errors` in `wrap_errors` (PR #229 by @laazy) +- test failures under Python 3.13 (issue #228 by @mgorny). Version 0.31.2 (2024-01-24) --------------------------- From c16395ddfb77dda7e966ec1adff30e4758191aea Mon Sep 17 00:00:00 2001 From: Andy Mikhailenko Date: Sat, 13 Jul 2024 19:42:01 +0200 Subject: [PATCH 10/11] fix: exposing func arg in CLI under a different name via deco (fixes #224) (#230) --- CHANGELOG.rst | 6 ++++-- src/argh/decorators.py | 6 +++++- tests/test_regressions.py | 19 +++++++++++++++++++ 3 files changed, 28 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 77c9fcd..63daf4b 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -6,8 +6,10 @@ Version 0.31.3 Bugs fixed: -- fix type annotation of `errors` in `wrap_errors` (PR #229 by @laazy) -- test failures under Python 3.13 (issue #228 by @mgorny). +- wrong type annotation of `errors` in `wrap_errors` (PR #229 by @laazy) +- tests were failing under Python 3.13 (issue #228 by @mgorny) +- regression: can't set argument name with `dest` via decorator + (issue #224 by @mathieulongtin) Version 0.31.2 (2024-01-24) --------------------------- diff --git a/src/argh/decorators.py b/src/argh/decorators.py index ff0d2be..0de1c98 100644 --- a/src/argh/decorators.py +++ b/src/argh/decorators.py @@ -140,7 +140,11 @@ def wrapper(func: Callable) -> Callable: if not args: raise CliArgToFuncArgGuessingError("at least one CLI arg must be defined") - func_arg_name = naive_guess_func_arg_name(args) + if "dest" in kwargs: + func_arg_name = kwargs.pop("dest") + else: + func_arg_name = naive_guess_func_arg_name(args) + cli_arg_names = [name.replace("_", "-") for name in args] completer = kwargs.pop("completer", None) spec = ParserAddArgumentSpec.make_from_kwargs( diff --git a/tests/test_regressions.py b/tests/test_regressions.py index 343da81..2d5b7fd 100644 --- a/tests/test_regressions.py +++ b/tests/test_regressions.py @@ -242,3 +242,22 @@ def func(*, paths: Optional[List[str]] = ["one", "two"]): assert run(parser, "").out == "['one', 'two']\n" assert run(parser, "--paths alpha").out == "['alpha']\n" assert run(parser, "--paths alpha beta gamma").out == "['alpha', 'beta', 'gamma']\n" + + +def test_regression_issue224(): + """ + Issue #224: @arg param `dest` was ignored and Argh was unable to map the + declaration onto the function signature. + + Use case: expose a function argument with a different name in the CLI. + """ + + @argh.arg("-l", dest="list_files") + def func(*, list_files=False): + return f"list_files={list_files}" + + parser = DebugArghParser() + parser.set_default_command(func) + + assert run(parser, "").out == "list_files=False\n" + assert run(parser, "-l").out == "list_files=True\n" From 21528aefc839458322f70aac7bf0949f82e60780 Mon Sep 17 00:00:00 2001 From: Andy Mikhaylenko Date: Sat, 13 Jul 2024 19:43:41 +0200 Subject: [PATCH 11/11] docs: add release date in changelog --- CHANGELOG.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 63daf4b..a640a82 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,8 +1,8 @@ Changelog ========= -Version 0.31.3 --------------- +Version 0.31.3 (2024-07-13) +--------------------------- Bugs fixed: