From a1fe56f6b21a63681e46cfca230fbe94ccfd28c9 Mon Sep 17 00:00:00 2001 From: RF-Tar-Railt Date: Fri, 25 Oct 2024 11:09:23 +0800 Subject: [PATCH] :beers: @deprecated for v1 --- src/arclet/alconna/__init__.py | 5 +--- src/arclet/alconna/args.py | 6 +++-- src/arclet/alconna/core.py | 2 ++ src/arclet/alconna/ingedia/_argv.py | 17 ++++++------ src/arclet/alconna/v1/__init__.py | 4 +++ src/arclet/alconna/v1/compat.py | 2 ++ src/arclet/alconna/v1/duplication.py | 3 +++ src/arclet/alconna/v1/stub.py | 5 +++- src/arclet/alconna/v1/typing.py | 5 ++++ tests/analyser_test.py | 27 +++++++++++-------- tests/args_test.py | 40 +++++++++++++++++----------- tests/base_test.py | 6 ++--- tests/core_test.py | 13 +++++++++ tests/devtool.py | 6 ++--- 14 files changed, 93 insertions(+), 48 deletions(-) diff --git a/src/arclet/alconna/__init__.py b/src/arclet/alconna/__init__.py index e7b7f9a..087555d 100644 --- a/src/arclet/alconna/__init__.py +++ b/src/arclet/alconna/__init__.py @@ -36,11 +36,8 @@ from .exceptions import NullMessage as NullMessage from .exceptions import ParamsUnmatched as ParamsUnmatched from .formatter import TextFormatter as TextFormatter -from .manager import ShortcutArgs as ShortcutArgs +from .shortcut import ShortcutArgs as ShortcutArgs from .manager import command_manager as command_manager from .typing import AllParam as AllParam __version__ = "1.8.31" - -# backward compatibility -AnyOne = ANY diff --git a/src/arclet/alconna/args.py b/src/arclet/alconna/args.py index 85c7dc3..f24037e 100644 --- a/src/arclet/alconna/args.py +++ b/src/arclet/alconna/args.py @@ -373,8 +373,10 @@ def __new__( if seps is not None: arg.field.seps = seps cls.__args_data__ = _Args(all_args, cls) - - dcls = dc.make_dataclass(cls.__name__, [(arg.name, arg.type_, arg.field.to_dc_field()) for arg in cls.__args_data__.data], namespace=types_namespace, repr=True) + try: + dcls = dc.make_dataclass(cls.__name__, [(arg.name, arg.type_, arg.field.to_dc_field()) for arg in cls.__args_data__.data], namespace=types_namespace, repr=True) + except TypeError as e: + raise TypeError(f"cannot create Args Model: {e}") from None cls.__init__ = dcls.__init__ # type: ignore if "__repr__" not in cls.__dict__: cls.__repr__ = dcls.__repr__ # type: ignore diff --git a/src/arclet/alconna/core.py b/src/arclet/alconna/core.py index e708207..85bf89f 100644 --- a/src/arclet/alconna/core.py +++ b/src/arclet/alconna/core.py @@ -433,6 +433,8 @@ def _parse(self, message: TDC, ctx: dict[str, Any] | None = None) -> Arparma[TDC return res analyser = command_manager.require(self) argv = analyser.argv + if message.__class__ is str: + message = [message] # type: ignore argv.enter(ctx).build(message) if argv.message_cache and (res := command_manager.get_record(argv.token)): return res diff --git a/src/arclet/alconna/ingedia/_argv.py b/src/arclet/alconna/ingedia/_argv.py index d1c62d2..51e2a20 100644 --- a/src/arclet/alconna/ingedia/_argv.py +++ b/src/arclet/alconna/ingedia/_argv.py @@ -120,15 +120,8 @@ def build(self, data: TDC) -> Self: """ self.reset() if self.checker and not self.checker(data): - if not self.converter: - raise TypeError(data) - try: - data = self.converter(data) # type: ignore - except Exception as e: - raise TypeError(data) from e + raise TypeError(data) self.origin = data - if data.__class__ is str: - data = [data] # type: ignore i = 0 raw_data = self.raw_data for unit in data: @@ -320,3 +313,11 @@ def argv_config( Argv._cache.setdefault(target or __argv_type__.get(), {}).update( {k: v for k, v in locals().items() if v is not None} ) + + +def reset_argv_config(target: type[Argv] | None = None): + """重置命令行参数配置""" + if target: + Argv._cache.pop(target, None) + else: + Argv._cache.clear() diff --git a/src/arclet/alconna/v1/__init__.py b/src/arclet/alconna/v1/__init__.py index ff5232c..e850aa0 100644 --- a/src/arclet/alconna/v1/__init__.py +++ b/src/arclet/alconna/v1/__init__.py @@ -56,3 +56,7 @@ from .typing import StrMulti as StrMulti from .typing import UnpackVar as UnpackVar from .typing import Up as Up + + +# backward compatibility +AnyOne = ANY diff --git a/src/arclet/alconna/v1/compat.py b/src/arclet/alconna/v1/compat.py index de3e4a3..2e76f22 100644 --- a/src/arclet/alconna/v1/compat.py +++ b/src/arclet/alconna/v1/compat.py @@ -1,5 +1,6 @@ from __future__ import annotations +import warnings from typing import Literal, Any, Callable, ContextManager from typing_extensions import deprecated from arclet.alconna import Metadata, Config, global_config @@ -93,6 +94,7 @@ def __setattr__(self, key, value): elif key in ("name", "prefixes", "formatter_type", "to_text", "converter"): setattr(self.origin, key, value) else: + warnings.warn(f"Namespace.{key} is deprecated, use Namespace.config.{key} instead", DeprecationWarning, stacklevel=2) setattr(self.origin.config, key, value) diff --git a/src/arclet/alconna/v1/duplication.py b/src/arclet/alconna/v1/duplication.py index 733495c..ef8c7e5 100644 --- a/src/arclet/alconna/v1/duplication.py +++ b/src/arclet/alconna/v1/duplication.py @@ -2,6 +2,7 @@ from inspect import isclass from typing import cast +from typing_extensions import deprecated from tarina import Empty @@ -12,6 +13,7 @@ from .stub import ArgsStub, BaseStub, OptionStub, SubcommandStub +@deprecated("Duplication is removed", category=DeprecationWarning, stacklevel=1) class Duplication: """`副本`, 用以更方便的检查、调用解析结果的类。""" @@ -47,6 +49,7 @@ def subcommand(self, name: str) -> SubcommandStub | None: return cast(SubcommandStub, getattr(self, name, None)) +@deprecated("generate_duplication is removed", category=DeprecationWarning, stacklevel=1) def generate_duplication(alc: Alconna) -> type[Duplication]: """依据给定的命令生成一个解析结果的检查类。""" diff --git a/src/arclet/alconna/v1/stub.py b/src/arclet/alconna/v1/stub.py index 065bce3..2761dda 100644 --- a/src/arclet/alconna/v1/stub.py +++ b/src/arclet/alconna/v1/stub.py @@ -4,7 +4,7 @@ from dataclasses import dataclass, field from inspect import isclass from typing import Any, Generic, TypeVar -from typing_extensions import Self +from typing_extensions import Self, deprecated from nepattern import ANY, BasePattern @@ -40,6 +40,7 @@ def __repr__(self): return f"{{{', '.join([f'{k}={v}' for k, v in vars(self).items() if v and not k.startswith('_')])}}}" +@deprecated("ArgsStub is removed, use `ArgsBase` instead", category=DeprecationWarning, stacklevel=1) @dataclass(init=True) class ArgsStub(BaseStub[_Args]): """参数存根""" @@ -103,6 +104,7 @@ def __getitem__(self, item: int | str) -> Any: return self._value[item] +@deprecated("OptionStub is removed", category=DeprecationWarning, stacklevel=1) @dataclass(init=True) class OptionStub(BaseStub[Option]): """选项存根""" @@ -130,6 +132,7 @@ def set_result(self, result: OptionResult | None): return self +@deprecated("SubcommandStub is removed", category=DeprecationWarning, stacklevel=1) @dataclass(init=True) class SubcommandStub(BaseStub[Subcommand]): """子命令存根""" diff --git a/src/arclet/alconna/v1/typing.py b/src/arclet/alconna/v1/typing.py index d2a6bf8..07f69e8 100644 --- a/src/arclet/alconna/v1/typing.py +++ b/src/arclet/alconna/v1/typing.py @@ -6,6 +6,7 @@ Literal, TypeVar, ) +from typing_extensions import deprecated from nepattern import BasePattern, MatchMode, parser @@ -14,6 +15,7 @@ T = TypeVar("T") +@deprecated("KeyWordVar is deprecated, use `Field(kw_only=True)` instead", category=DeprecationWarning, stacklevel=1) class KeyWordVar(BasePattern[T, Any, Literal[MatchMode.KEEP]]): """对具名参数的包装""" @@ -45,6 +47,7 @@ def __getitem__(self, item: BasePattern[T, Any, Any] | type[T] | Any): __rmatmul__ = __getitem__ +@deprecated("MultiVar is deprecated, use `Field(multiple=...)` instead", category=DeprecationWarning, stacklevel=1) class MultiVar(BasePattern[T, Any, Literal[MatchMode.KEEP]]): """对可变参数的包装""" @@ -87,6 +90,7 @@ class KWBool(BasePattern): """对布尔参数的包装""" +@deprecated("UnpackVar is deprecated, use `ArgsBase` instead", category=DeprecationWarning, stacklevel=1) class UnpackVar(BasePattern): """特殊参数,利用dataclass 的 field 生成 arg 信息,并返回dcls""" @@ -114,6 +118,7 @@ def __mul__(self, other): Up = _Up() +@deprecated("StrMulti is deprecated, use `Field(multiple=str)` instead", stacklevel=1) class _StrMulti(MultiVar[str]): pass diff --git a/tests/analyser_test.py b/tests/analyser_test.py index ba9c79f..e0c5c90 100644 --- a/tests/analyser_test.py +++ b/tests/analyser_test.py @@ -1,10 +1,12 @@ from dataclasses import dataclass, field -from typing import Any, Union +from typing import Union +from collections import UserList +import pytest from nepattern import BasePattern, MatchMode from arclet.alconna import Alconna, Args, Option -from arclet.alconna.ingedia._argv import argv_config +from arclet.alconna.ingedia._argv import argv_config, reset_argv_config @dataclass @@ -49,7 +51,7 @@ def test_filter_out(): ana = Alconna("ana", Args.foo(str)) assert ana.parse(["ana", 123, "bar"]).matched is True assert ana.parse("ana bar").matched is True - argv_config(filter_out=[]) + reset_argv_config() ana_1 = Alconna("ana", Args.foo(str)) assert ana_1.parse(["ana", 123, "bar"]).matched is False @@ -59,7 +61,7 @@ def test_preprocessor(): ana1 = Alconna("ana1", Args.bar(int)) assert ana1.parse(["ana1", [1, 2, 3]]).matched is True assert ana1.parse(["ana1", [1, 2, 3]]).bar == 3 - argv_config(preprocessors={}) + reset_argv_config() ana1_1 = Alconna("ana1", Args.bar(int)) assert ana1_1.parse(["ana1", [1, 2, 3]]).matched is False @@ -72,7 +74,7 @@ def test_with_set_unit(): assert res.matched is True assert res.foo.data["qq"] == "123456" assert not ana2.parse([Segment.text("ana2"), Segment.face(103), Segment.at(123456)]).matched - argv_config() + reset_argv_config() def test_unhashable_unit(): @@ -88,15 +90,18 @@ def test_unhashable_unit(): print(ana3_1.parse(["ana3_1", "--foo", "--comp", Segment.at(123)])) print(ana3_1.parse(["ana3_1", "--comp", Segment.at(123)])) + reset_argv_config() + def test_checker(): - argv_config(checker=lambda x: isinstance(x, list)) + argv_config(checker=lambda x: isinstance(x, UserList)) ana4 = Alconna("ana4", Args.foo(int)) - print(ana4.parse(["ana4", "123"])) - try: - print(ana4.parse("ana4 123")) - except TypeError as e: - print(e) + assert ana4.parse(UserList(["ana4", "123"])) + + with pytest.raises(TypeError): + ana4.parse("ana4 123") + + reset_argv_config() if __name__ == "__main__": diff --git a/tests/args_test.py b/tests/args_test.py index 24a0411..9be8f70 100644 --- a/tests/args_test.py +++ b/tests/args_test.py @@ -1,8 +1,10 @@ +import pytest + from typing import Union from nepattern import INTEGER, BasePattern, MatchMode, combine -from arclet.alconna import Args, Arg +from arclet.alconna import Args, Arg, ArgsBase, arg_field from devtool import analyse_args @@ -201,21 +203,27 @@ def test_annotated(): assert analyse_args(arg18, ["0 0"], raise_exception=False) != {"foo": 0, "bar": 0} -# def test_unpack(): -# from dataclasses import dataclass, field -# -# from arclet.alconna.typing import UnpackVar -# -# @dataclass -# class People: -# name: str -# age: int = field(default=16) -# -# arg19 = Args["people", UnpackVar(People)] -# assert analyse_args(arg19, ["alice", 16]) == {"people": People("alice", 16)} -# assert analyse_args(arg19, ["bob"]) == {"people": People("bob", 16)} -# arg19_1 = Args["people", UnpackVar(People, kw_only=True)].separate("&") -# assert analyse_args(arg19_1, ["name=alice&age=16"]) == {"people": People("alice", 16)} +def test_args_model(): + class People(ArgsBase): + name: str + age: int = arg_field(default=16) + + assert analyse_args(People, ["abc"]) == {"name": "abc", "age": 16} + + class Foo(ArgsBase): + foo: str + + class Bar(ArgsBase): + bar: int = arg_field(42) + + class Baz(Bar, Foo): + baz: bool = arg_field(True) + + assert analyse_args(Baz, ["abc 123"]) == {"foo": "abc", "bar": 123, "baz": True} + + with pytest.raises(TypeError, match="cannot create Args Model: non-default argument 'foo' follows default argument"): + class Baz1(Foo, Bar): + baz: bool = arg_field(True) def test_multi_multi(): diff --git a/tests/base_test.py b/tests/base_test.py index 7c0229d..8910424 100644 --- a/tests/base_test.py +++ b/tests/base_test.py @@ -21,7 +21,7 @@ def test_option_aliases(): def test_separator(): opt2 = Option("foo", Args.bar(int), separators="|") - assert analyse_option(opt2, "foo|123") == OptionResult(None, {"bar": 123}) + assert analyse_option(opt2, ["foo|123"]) == OptionResult(None, {"bar": 123}) opt2_1 = Option("foo", Args.bar(int)).separate("|") assert opt2 == opt2_1 @@ -29,12 +29,12 @@ def test_separator(): def test_subcommand(): sub = Subcommand("test", Option("foo"), Option("bar")) assert len(sub.options) == 2 - assert analyse_subcommand(sub, "test foo") == SubcommandResult(None, {}, {"foo": OptionResult()}) + assert analyse_subcommand(sub, ["test foo"]) == SubcommandResult(None, {}, {"foo": OptionResult()}) def test_compact(): opt3 = Option("-Foo", Args.bar(int), compact=True) - assert analyse_option(opt3, "-Foo123") == OptionResult(None, {"bar": 123}) + assert analyse_option(opt3, ["-Foo123"]) == OptionResult(None, {"bar": 123}) def test_add(): diff --git a/tests/core_test.py b/tests/core_test.py index 905d075..84c7fdb 100644 --- a/tests/core_test.py +++ b/tests/core_test.py @@ -723,6 +723,7 @@ def test_completion_interface(): def test_call(): from dataclasses import dataclass + from arclet.alconna import ArgsBase alc22 = Alconna("core22", Args.foo(int), Args.bar(str)) alc22("core22 123 abc") @@ -748,6 +749,18 @@ class A: alc22_1.parse("core22_1 abc") assert alc22_1.exec_result["A"] == A("abc") + class B(ArgsBase): + name: str + + alc22_2 = Alconna("core22_2", B) + + @alc22_2.bind(False) + def cb1(args: B): + return args.name + + alc22_2.parse("core22_2 abc") + assert cb1.result == "abc" + def test_nest_subcommand(): class A: diff --git a/tests/devtool.py b/tests/devtool.py index 80cb39c..5e96670 100644 --- a/tests/devtool.py +++ b/tests/devtool.py @@ -61,7 +61,7 @@ def analyse_args( def analyse_header( headers: list[str | Any] | list[tuple[Any, str]], command_name: str, - command: DataCollection[str | Any], + command: list[str | Any], sep: str = " ", compact: bool = False, raise_exception: bool = True, @@ -83,7 +83,7 @@ def analyse_header( def analyse_option( option: Option, - command: DataCollection[str | Any], + command: list[str | Any], raise_exception: bool = True, context_style: Literal["bracket", "parentheses"] | None = None, **kwargs, @@ -111,7 +111,7 @@ def analyse_option( def analyse_subcommand( subcommand: Subcommand, - command: DataCollection[str | Any], + command: list[str | Any], raise_exception: bool = True, context_style: Literal["bracket", "parentheses"] | None = None, **kwargs,