Skip to content

Commit

Permalink
💥 remove kw_only
Browse files Browse the repository at this point in the history
  • Loading branch information
RF-Tar-Railt committed Jan 8, 2025
1 parent 068e520 commit 03960fb
Show file tree
Hide file tree
Showing 7 changed files with 78 additions and 160 deletions.
4 changes: 2 additions & 2 deletions exam8.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

class Foo(ArgsBase):
foo: str
bar: int = arg_field(42, kw_only=True)
bar: int = arg_field(42)


alc = Alconna("test", Foo)
Expand All @@ -15,7 +15,7 @@ def cb(args: Foo):
print(args.foo, args.bar)


print(alc.parse("test abc bar=123"))
print(alc.parse("test abc 123"))


class Bar(ArgsBase):
Expand Down
48 changes: 0 additions & 48 deletions example/endpoint.py

This file was deleted.

75 changes: 37 additions & 38 deletions src/arclet/alconna/args.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,9 @@ class Field(Generic[_T]):
"""参数单元使用的分隔符"""
optional: bool = dc.field(default=False, compare=False, hash=False)
hidden: bool = dc.field(default=False, compare=False, hash=False)
kw_only: bool = dc.field(default=False, compare=False, hash=False)
# kw_only: bool = dc.field(default=False, compare=False, hash=False)
multiple: bool | int | Literal["+", "*", "str"] = dc.field(default=False, compare=False, hash=False)
kw_sep: str = dc.field(default="=", compare=False, hash=False)
# kw_sep: str = dc.field(default="=", compare=False, hash=False)
wildcard: bool = dc.field(default=False, compare=False, hash=False)

@property
Expand Down Expand Up @@ -79,10 +79,10 @@ def get_missing_tips(self, fallback: str):

def to_dc_field(self):
if self.default_factory is not Empty:
return dc.field(default_factory=self.default_factory, **safe_field_kw(kw_only=self.kw_only))
return dc.field(default_factory=self.default_factory)
if self.default is not Empty:
return dc.field(default=self.default, **safe_field_kw(kw_only=self.kw_only))
return dc.field(**safe_field_kw(kw_only=self.kw_only))
return dc.field(default=self.default)
return dc.field()


def arg_field(
Expand All @@ -97,13 +97,11 @@ def arg_field(
notice: str | None = None,
seps: str = " ",
multiple: bool | int | Literal["+", "*", "str"] = False,
kw_only: bool = False,
kw_sep: str = "=",
optional: bool = False,
hidden: bool = False,
wildcard: bool = False,
) -> "Any":
return Field(default, default_factory, alias, completion, unmatch_tips, missing_tips, notice, seps, optional, hidden, kw_only, multiple, kw_sep, wildcard)
return Field(default, default_factory, alias, completion, unmatch_tips, missing_tips, notice, seps, optional, hidden, multiple, wildcard)


@dc.dataclass(**safe_dcls_kw(init=False, eq=True, unsafe_hash=True, slots=True))
Expand Down Expand Up @@ -187,8 +185,8 @@ def type_display(self):
if self.field.hidden:
return "***"
v = str(self.type_)
if self.field.kw_only:
v = f"{self.field.kw_sep}{v}"
# if self.field.kw_only:
# v = f"{self.field.kw_sep}{v}"
if self.field.multiple is not False:
if self.field.multiple is True:
v = f"({v}+)"
Expand All @@ -208,33 +206,29 @@ def __init__(self, args: list[Arg[Any]], origin: type[ArgsBase] | None = None):
self.origin = origin
self.optional_count = 0
normal = []
keyword = []
vars_positional: list[Arg[Any]] = []
vars_keyword: list[Arg[Any]] = []
for arg in args:
if arg.field.multiple is not False:
if arg.field.kw_only:
for a in vars_positional:
if arg.field.kw_sep in a.field.seps:
raise InvalidArgs("varkey cannot use the same sep as varpos's Arg")
vars_keyword.append(arg)
# if arg.field.kw_only:
# for a in vars_positional:
# if arg.field.kw_sep in a.field.seps:
# raise InvalidArgs("varkey cannot use the same sep as varpos's Arg")
# vars_keyword.append(arg)
# elif self.keyword_only:
# raise InvalidArgs(i18n.require("args.exclude_mutable_args"))
else:
vars_positional.append(arg)
elif arg.field.kw_only:
# if self.vars_keyword:
# raise InvalidArgs(i18n.require("args.exclude_mutable_args"))
keyword.append(arg)
vars_positional.append(arg)
# elif arg.field.kw_only:
# # if self.vars_keyword:
# # raise InvalidArgs(i18n.require("args.exclude_mutable_args"))
# keyword.append(arg)
else:
normal.append(arg)
if arg.field.optional:
self.optional_count += 1
elif not arg.field.no_default:
self.optional_count += 1
normal.extend(vars_positional)
keyword.extend(vars_keyword)
self.data: list[Arg[Any]] = normal + keyword
self.data: list[Arg[Any]] = normal
self.count = len(self.data)

def __iter__(self):
Expand Down Expand Up @@ -309,15 +303,14 @@ def _is_classvar(a_type):
and a_type.__origin__ is typing.ClassVar))


@dataclass_transform(field_specifiers=(arg_field,))
@dataclass_transform(field_specifiers=(arg_field,), kw_only_default=True)
class ArgsMeta(type):
def __new__(
mcs,
name: str,
bases: tuple[type, ...],
namespace: dict[str, Any],
*,
kw_only: bool | None = None,
seps: str | None = None,
**kwargs,
):
Expand Down Expand Up @@ -345,26 +338,32 @@ def __new__(
if field.default is Empty and field.default_factory is Empty:
delattr(cls, name)
if field.multiple is not False:
if not field.kw_only:
if get_origin(typ) is tuple:
typ = get_args(typ)[0]
elif field.multiple != "str" or typ is not str:
raise TypeError(f"{name!r} is a varpos but does not have a tuple type annotation")
elif get_origin(typ) is not dict:
raise TypeError(f"{name!r} is a varkey but does not have a dict type annotation")
# if not field.kw_only:
if get_origin(typ) is tuple:
typ = get_args(typ)[0]
elif field.multiple != "str" or typ is not str:
raise TypeError(f"{name!r} is a varpos but does not have a tuple type annotation")
# elif get_origin(typ) is not dict:
# raise TypeError(f"{name!r} is a varkey but does not have a dict type annotation")
cls_args.append(Arg(name, typ, field))
for name, value in cls.__dict__.items():
if isinstance(value, Field) and name not in cls_annotations:
raise TypeError(f"{name!r} is a Field but has no type annotation")
all_args = data_args + cls_args
for arg in all_args:
if kw_only is not None:
arg.field.kw_only = kw_only
# if kw_only is not None:
# arg.field.kw_only = kw_only
if seps is not None:
arg.field.seps = seps
cls.__args_data__ = _Args(all_args, cls)
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)
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,
**safe_dcls_kw(kw_only=True) # type: ignore
)
except TypeError as e:
raise TypeError(f"cannot create Args Model: {e}") from None
cls.__init__ = dcls.__init__ # type: ignore
Expand All @@ -377,7 +376,7 @@ class ArgsBase(metaclass=ArgsMeta):
__args_data__: ClassVar[_Args]

if not TYPE_CHECKING:
def __init__(self, *args, **kwargs): # for pycharm type check
def __init__(self, **kwargs): # for pycharm type check
pass

def dump(self):
Expand Down
35 changes: 1 addition & 34 deletions src/arclet/alconna/ingedia/_handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,24 +86,6 @@ def step_multiple(argv: Argv, ana: Analyser, arg: Arg[Any], result: dict[str, An
if arg.name not in result:
result[arg.name] = ()
return True
if field.kw_only:
if not _str:
raise InvalidParam(i18n.require("args.key_missing").format(target=may_arg, key=arg.name), arg)
key, _m_arg = split_once(may_arg, field.kw_sep, argv.filter_crlf)
key: str = pat.fullmatch(key)["name"] # type: ignore
if _m_arg:
may_arg = _m_arg
else:
may_arg, _str = argv.next(field.seps)
ans = _handle_arg(argv, arg, may_arg, _str)
if ans is Empty:
return True
if arg.name not in result:
result[arg.name] = []
result[arg.name].append((key, ans))
if field.multiple is not True and isinstance(field.multiple, int):
return len(result[arg.name]) >= field.multiple
return False
try:
ans = _handle_arg(argv, arg, may_arg, _str)
except InvalidParam:
Expand Down Expand Up @@ -168,28 +150,13 @@ def analyse_args(analyser: Analyser, argv: Argv, args: _Args) -> dict[str, Any]:
raise ArgumentMissing(field.get_missing_tips(i18n.require("args.missing").format(key=arg.name)), arg)
index += 1
continue
if field.kw_only:
if not _str:
raise InvalidParam(i18n.require("args.key_missing").format(target=may_arg, key=arg.name), arg)
key, _m_arg = split_once(may_arg, field.kw_sep, argv.filter_crlf)
key: str = pat.fullmatch(key)["name"] # type: ignore
if key != arg.name:
if levenshtein(key, arg.name) >= argv.fuzzy_threshold:
raise FuzzyMatchSuccess(i18n.require("fuzzy.matched").format(source=arg.name, target=key))
raise InvalidParam(i18n.require("args.key_not_found").format(name=key), arg)
if _m_arg:
may_arg = _m_arg
else:
may_arg, _str = argv.next(field.seps)
ans = _handle_arg(argv, arg, may_arg, _str)
if ans is not Empty:
result[arg.name] = ans
index += 1
continue
elif step_multiple(argv, analyser, arg, result):
if arg.field.kw_only:
result[arg.name] = dict(result[arg.name])
elif arg.field.multiple == "str":
if arg.field.multiple == "str":
result[arg.name] = arg.field.seps[0].join(result[arg.name])
else:
result[arg.name] = tuple(result[arg.name])
Expand Down
16 changes: 8 additions & 8 deletions src/arclet/alconna/v1/args.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,14 @@ def build(self):
else:
arg.field.multiple = value.flag if value.length < 1 else value.length
arg.type_ = value.base
if isinstance(value.base, KeyWordVar):
arg.type_ = value.base.base
arg.field.kw_only = True
arg.field.kw_sep = value.base.sep
elif isinstance(value, KeyWordVar):
arg.field.kw_only = True
arg.field.kw_sep = value.sep
arg.type_ = value.base
# if isinstance(value.base, KeyWordVar):
# arg.type_ = value.base.base
# arg.field.kw_only = True
# arg.field.kw_sep = value.base.sep
# elif isinstance(value, KeyWordVar):
# arg.field.kw_only = True
# arg.field.kw_sep = value.sep
# arg.type_ = value.base
elif isinstance(value, UnpackVar):
arg.type_ = Pattern(value.origin)
elif isinstance(value, _AllParamPattern):
Expand Down
58 changes: 29 additions & 29 deletions tests/args_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,8 @@ def test_multi():
arg8 = Args.multi(str, multiple=True)
assert analyse_args(arg8, ["a b c d"]).get("multi") == ("a", "b", "c", "d")
assert analyse_args(arg8, [], raise_exception=False) != {"multi": ()}
arg8_1 = Args.kwargs(str, multiple=True, kw_only=True)
assert analyse_args(arg8_1, ["a=b c=d"]).get("kwargs") == {"a": "b", "c": "d"}
# arg8_1 = Args.kwargs(str, multiple=True, kw_only=True)
# assert analyse_args(arg8_1, ["a=b c=d"]).get("kwargs") == {"a": "b", "c": "d"}
arg8_2 = Args.multi(int, multiple="*")
assert analyse_args(arg8_2, ["1 2 3 4"]).get("multi") == (1, 2, 3, 4)
assert analyse_args(arg8_2, []).get("multi") == ()
Expand Down Expand Up @@ -111,33 +111,33 @@ def test_optional():
assert analyse_args(arg13_2, ["abc"]) == {"foo": "abc"}


def test_kwonly():
arg14 = Args.foo(str).bar(int, kw_only=True)
assert analyse_args(arg14, ["abc bar=123"]) == {
"foo": "abc",
"bar": 123,
}
assert analyse_args(arg14, ["abc 123"], raise_exception=False) != {
"foo": "abc",
"bar": 123,
}
arg14_1 = Args.width(int, 1280, optional=True, kw_only=True).height(int, 960, optional=True, kw_only=True)
assert analyse_args(arg14_1, ["--width=960 --height=960"]) == {
"width": 960,
"height": 960,
}
arg14_2 = Args.foo(str).bar(int, kw_only=True, kw_sep=" ").baz(bool, kw_only=True, kw_sep=":")
assert analyse_args(arg14_2, ["abc -bar 123 baz:false"]) == {
"bar": 123,
"baz": False,
"foo": "abc",
}
arg14_3 = Args.foo(str).bar(int, kw_only=True).baz(bool, kw_only=True)
assert analyse_args(arg14_3, ["abc bar=456 baz=false"]) == {
"bar": 456,
"baz": False,
"foo": "abc",
}
# def test_kwonly():
# arg14 = Args.foo(str).bar(int, kw_only=True)
# assert analyse_args(arg14, ["abc bar=123"]) == {
# "foo": "abc",
# "bar": 123,
# }
# assert analyse_args(arg14, ["abc 123"], raise_exception=False) != {
# "foo": "abc",
# "bar": 123,
# }
# arg14_1 = Args.width(int, 1280, optional=True, kw_only=True).height(int, 960, optional=True, kw_only=True)
# assert analyse_args(arg14_1, ["--width=960 --height=960"]) == {
# "width": 960,
# "height": 960,
# }
# arg14_2 = Args.foo(str).bar(int, kw_only=True, kw_sep=" ").baz(bool, kw_only=True, kw_sep=":")
# assert analyse_args(arg14_2, ["abc -bar 123 baz:false"]) == {
# "bar": 123,
# "baz": False,
# "foo": "abc",
# }
# arg14_3 = Args.foo(str).bar(int, kw_only=True).baz(bool, kw_only=True)
# assert analyse_args(arg14_3, ["abc bar=456 baz=false"]) == {
# "bar": 456,
# "baz": False,
# "foo": "abc",
# }


def test_pattern():
Expand Down
2 changes: 1 addition & 1 deletion tests/core_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -622,7 +622,7 @@ def test_hide_annotation():


def test_args_notice():
alc19 = Alconna("core19", Args.foo(int, optional=True, notice="A TEST")) + Option("bar", Args.baz(str, notice="ANOTHER TEST", kw_only=True))
alc19 = Alconna("core19", Args.foo(int, optional=True, notice="A TEST")) + Option("bar", Args.baz(str, notice="ANOTHER TEST"))
print("")
print(alc19.get_help())

Expand Down

0 comments on commit 03960fb

Please sign in to comment.