Skip to content

Commit 227618f

Browse files
authored
Merge pull request #165 from bearomorphism/typed
refactor: update linter tools, python dependency and add types to app…
2 parents bd8fe94 + 172351e commit 227618f

File tree

10 files changed

+241
-345
lines changed

10 files changed

+241
-345
lines changed

.github/workflows/test.yaml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ jobs:
1212
runs-on: ubuntu-latest
1313
strategy:
1414
matrix:
15-
python-version: ["3.8", "3.9", "3.10", "3.11"]
15+
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
1616
steps:
1717
- uses: actions/checkout@v3
1818
with:
@@ -30,13 +30,14 @@ jobs:
3030
virtualenvs-create: true
3131
- name: Install Dependencies
3232
run: |
33-
poetry install --no-interaction
33+
poetry install --all-groups --no-interaction
3434
- name: Test and Lint
3535
run: |
3636
./scripts/test
3737
- name: Upload coverage to Codecov
3838
uses: codecov/codecov-action@v3
3939
with:
40+
token: ${{ secrets.CODECOV_TOKEN }}
4041
file: ./coverage.xml
4142
flags: unittests
4243
name: decli

README.rst

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -688,7 +688,13 @@ Contributing
688688

689689
poetry install
690690

691-
3. Run tests
691+
3. Format
692+
693+
::
694+
695+
./scripts/format
696+
697+
4. Run tests
692698

693699
::
694700

decli/application.py

Lines changed: 43 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,32 @@
1-
import argparse
1+
from __future__ import annotations
2+
23
import logging
3-
from typing import Optional, Callable, Union
4+
from argparse import (
5+
ArgumentParser,
6+
_ArgumentGroup,
7+
_MutuallyExclusiveGroup,
8+
_SubParsersAction,
9+
)
10+
from collections.abc import Generator, Iterable
411
from copy import deepcopy
5-
12+
from typing import Any, Callable
613

714
config: dict = {}
815
logger = logging.getLogger(__name__)
916
logger.setLevel(logging.INFO)
1017

1118

12-
def init_config():
19+
def init_config() -> dict[str, Any]:
1320
return {"prefix_chars": "-"}
1421

1522

16-
def ensure_list(name: Union[str, list]) -> list:
23+
def ensure_list(name: str | list[str]) -> list[str]:
1724
if isinstance(name, str):
18-
name = [name]
25+
return [name]
1926
return name
2027

2128

22-
def has_many_and_is_optional(names: list) -> list:
29+
def has_many_and_is_optional(names: list[str]) -> list[str]:
2330
"""The arguments can have aliases only when they are optional.
2431
2532
If this is not the case, then it raises an error.
@@ -28,21 +35,18 @@ def has_many_and_is_optional(names: list) -> list:
2835
is_optional = all(name.startswith(tuple(prefix_chars)) for name in names)
2936

3037
if not is_optional and len(names) > 1:
31-
msg = (
32-
f"Only optional arguments (starting with {prefix_chars}) "
33-
"can have aliases"
38+
raise ValueError(
39+
f"Only optional arguments (starting with {prefix_chars}) can have aliases"
3440
)
35-
raise ValueError(msg)
3641
return names
3742

3843

39-
def is_exclusive_group_or_not(arg: dict):
44+
def is_exclusive_group_or_not(arg: dict) -> None:
4045
if "exclusive_group" in arg and "group" in arg:
41-
msg = "choose group or exclusive_group not both."
42-
raise ValueError(msg)
46+
raise ValueError("choose group or exclusive_group not both.")
4347

4448

45-
def validate_args(args: list):
49+
def validate_args(args: Iterable[dict]) -> Generator[dict, Any, None]:
4650
for arg in args:
4751
arg["name"] = ensure_list(arg["name"])
4852
has_many_and_is_optional(arg["name"])
@@ -51,11 +55,11 @@ def validate_args(args: list):
5155

5256

5357
def get_or_create_group(
54-
parser,
58+
parser: ArgumentParser,
5559
groups: dict,
56-
title: Optional[str] = None,
57-
description: Optional[str] = None,
58-
):
60+
title: str | None = None,
61+
description: str | None = None,
62+
) -> _ArgumentGroup | Any:
5963
group_parser = groups.get(title)
6064
if group_parser is None:
6165
group_parser = parser.add_argument_group(title, description)
@@ -64,8 +68,11 @@ def get_or_create_group(
6468

6569

6670
def get_or_create_exclusive_group(
67-
parser, groups: dict, title: Optional[str] = None, required=False
68-
):
71+
parser: ArgumentParser,
72+
groups: dict,
73+
title: str | None = None,
74+
required: bool = False,
75+
) -> _MutuallyExclusiveGroup | Any:
6976
group_parser = groups.get(title)
7077
if group_parser is None:
7178
group_parser = parser.add_mutually_exclusive_group(required=required)
@@ -74,18 +81,18 @@ def get_or_create_exclusive_group(
7481
return group_parser
7582

7683

77-
def add_arguments(parser, args: list):
84+
def add_arguments(parser: ArgumentParser, args: list) -> None:
7885
groups: dict = {}
7986
exclusive_groups: dict = {}
8087

8188
for arg in validate_args(args):
8289
logger.debug("arg: %s", arg)
8390

8491
name: list = arg.pop("name")
85-
group_title: Optional[str] = arg.pop("group", None)
86-
exclusive_group_title: Optional[str] = arg.pop("exclusive_group", None)
92+
group_title: str | None = arg.pop("group", None)
93+
exclusive_group_title: str | None = arg.pop("exclusive_group", None)
8794

88-
_parser = parser
95+
_parser: Any = parser
8996
if exclusive_group_title:
9097
logger.debug("Exclusive group: %s", exclusive_group_title)
9198
_parser = get_or_create_exclusive_group(
@@ -98,9 +105,9 @@ def add_arguments(parser, args: list):
98105
_parser.add_argument(*name, **arg)
99106

100107

101-
def add_subcommand(parser, command: dict):
108+
def add_subcommand(parser: _SubParsersAction[ArgumentParser], command: dict) -> None:
102109
args: list = command.pop("arguments", None)
103-
func: Optional[Callable] = command.pop("func", None)
110+
func: Callable | None = command.pop("func", None)
104111

105112
names: list = ensure_list(command.pop("name"))
106113
name: str = names.pop(0)
@@ -116,7 +123,7 @@ def add_subcommand(parser, command: dict):
116123
add_arguments(command_parser, args)
117124

118125

119-
def add_subparser(parser, subcommand: dict):
126+
def add_subparser(parser: ArgumentParser, subcommand: dict) -> None:
120127
commands: list = subcommand.pop("commands")
121128

122129
# This design is for python 3.6 compatibility
@@ -131,12 +138,14 @@ def add_subparser(parser, subcommand: dict):
131138
add_subcommand(subparser, command)
132139

133140

134-
def add_parser(data: dict, parser_class: Callable, parents: Optional[list]):
141+
def add_parser(
142+
data: dict, parser_class: Callable[..., ArgumentParser], parents: list | None
143+
) -> ArgumentParser:
135144
if parents is None:
136145
parents = []
137146

138-
args: Optional[list] = data.pop("arguments", None)
139-
subcommands: Optional[dict] = data.pop("subcommands", None)
147+
args: list | None = data.pop("arguments", None)
148+
subcommands: dict | None = data.pop("subcommands", None)
140149

141150
parser = parser_class(**data, parents=parents)
142151

@@ -153,9 +162,9 @@ def add_parser(data: dict, parser_class: Callable, parents: Optional[list]):
153162

154163
def cli(
155164
data: dict,
156-
parser_class: Callable = argparse.ArgumentParser,
157-
parents: Optional[list] = None,
158-
):
165+
parser_class: Callable[..., ArgumentParser] = ArgumentParser,
166+
parents: list | None = None,
167+
) -> ArgumentParser:
159168
"""Create a cli application.
160169
161170
This is the entrypoint.

examples/demo.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
"""Simple app example"""
2+
23
from decli import cli
34

45
data = {

0 commit comments

Comments
 (0)