Skip to content

Commit e94f880

Browse files
committed
Merge branch 'release/v0.2.0'
2 parents 707072e + ca2bd2d commit e94f880

25 files changed

Lines changed: 1100 additions & 562 deletions
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# This workflows will upload a Python Package using Twine when a release is created
2+
# For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries
3+
4+
name: Upload Python Package
5+
6+
on:
7+
release:
8+
types: [ created ]
9+
10+
jobs:
11+
deploy:
12+
13+
runs-on: ubuntu-latest
14+
15+
steps:
16+
- uses: actions/checkout@v2
17+
- name: Set up Python
18+
uses: actions/setup-python@v2
19+
with:
20+
python-version: '3.7'
21+
- name: Install dependencies
22+
run: |
23+
python -m pip install --upgrade pip
24+
python -m pip install setuptools wheel twine
25+
- name: Build and publish
26+
env:
27+
TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }}
28+
TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
29+
run: |
30+
python setup.py sdist bdist_wheel
31+
twine upload dist/*

.github/workflows/unit-tests.yml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
name: Unit Tests
2+
3+
on: [ push ]
4+
5+
jobs:
6+
build:
7+
8+
runs-on: ubuntu-latest
9+
10+
steps:
11+
- uses: actions/checkout@v2
12+
- name: Set up Python 3.7
13+
uses: actions/setup-python@v2
14+
with:
15+
python-version: 3.7
16+
- name: Install dependencies
17+
run: |
18+
python -m pip install --upgrade pip
19+
python -m pip install -e '.[dev]'
20+
- name: Test with pytest
21+
run: |
22+
python -m pytest tests

.travis.yml

Lines changed: 0 additions & 16 deletions
This file was deleted.

README.md

Lines changed: 234 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,88 +1,277 @@
1-
# aku
1+
# Aku
22

3-
[![PyPI Version](https://badge.fury.io/py/aku.svg)](https://pypi.org/project/aku/)
4-
[![Build Status](https://travis-ci.org/speedcell4/aku.svg?branch=master)](https://travis-ci.org/speedcell4/aku)
5-
[![Code Coverage](https://codecov.io/gh/speedcell4/aku/branch/master/graph/badge.svg)](https://codecov.io/gh/speedcell4/aku)
3+
[![Actions Status](https://github.com/speedcell4/aku/workflows/unit-tests/badge.svg)](https://github.com/speedcell4/aku/actions)
4+
[![PyPI version](https://badge.fury.io/py/aku.svg)](https://badge.fury.io/py/aku)
5+
[![Downloads](https://pepy.tech/badge/aku)](https://pepy.tech/project/aku)
66

7-
setup your argument parser speedily
7+
An interactive annotation-driven `ArgumentParser` generator
88

9-
## Installation
9+
## Requirements
1010

11-
```bash
12-
python3.6 -m pip install aku --upgrade
11+
* Python 3.7 or higher
12+
13+
## Install
14+
15+
```shell script
16+
python -m pip install aku --upgrade
1317
```
1418

19+
## Type Annotations
20+
21+
* primitive types,
22+
- e.g., `int`, `bool`, `str`, `float`, `Path`, etc.
23+
* container types
24+
- list `List[T]`
25+
- homogeneous tuple, e.g., `Tuple[T, ...]`
26+
- heterogeneous tuple, e.g., `Tuple[T1, T2, T3]`
27+
- literal, e.g., `Literal[42, 1905]`
28+
* nested types
29+
- function, e.g., `Type[<func_name>]`
30+
- union of functions, e.g., `Union[Type[<func1_name>], Type[<func2_name>], Type[<func3_name>]]`
31+
1532
## Usage
1633

34+
### Primitive Types
35+
36+
The key idea of aku to generate `ArgumentParser` according to the type annotations of functions. For example, to register single function with only primitive types,
37+
1738
```python
18-
# file test_single_function.py
19-
import aku
39+
from pathlib import Path
40+
41+
from aku import Aku
42+
43+
aku = Aku()
2044

21-
app = aku.Aku()
2245

46+
@aku.option
47+
def foo(a: int, b: bool = True, c: str = '3', d: float = 4.0, e: Path = Path.home()):
48+
print(f'a => {a}')
49+
print(f'b => {b}')
50+
print(f'c => {c}')
51+
print(f'd => {d}')
52+
print(f'e => {e}')
2353

24-
@app.register
25-
def add(a: int, b: int = 2):
26-
print(f'{a} + {b} => {a + b}')
2754

55+
aku.run()
56+
```
57+
58+
`aku` will generate a `ArgumentParser` which provides your command line interface looks like below,
2859

29-
app.run()
60+
```shell script
61+
~ python examples/foo.py --help
62+
usage: foo.py [-h] --a int [--b bool] [--c str] [--d float] [--e path]
63+
64+
optional arguments:
65+
-h, --help show this help message and exit
66+
--a int a
67+
--b bool b (default: True)
68+
--c str c (default: 3)
69+
--d float d (default: 4.0)
70+
--e path e (default: /Users/home)
3071
```
3172

32-
then `aku` will automatically add argument option according to your function signature.
73+
Of course you can achieve the same functions by instantiating an `ArgumentParser`, but `aku` certainly makes such steps simple and efficient.
74+
75+
```python
76+
from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter, SUPPRESS
77+
from pathlib import Path
78+
79+
80+
def tp_bool(arg_strings: str) -> bool:
81+
arg_strings = arg_strings.lower().strip()
82+
if arg_strings in ('t', 'true', 'y', 'yes', '1'):
83+
return True
84+
if arg_strings in ('f', 'false', 'n', 'no', '0'):
85+
return False
86+
raise ValueError
87+
88+
89+
def foo(a: int, b: bool = True, c: str = '3', d: float = 4.0, e: Path = Path.home()):
90+
print(f'a => {a}')
91+
print(f'b => {b}')
92+
print(f'c => {c}')
93+
print(f'd => {d}')
94+
print(f'e => {e}')
95+
96+
97+
argument_parser = ArgumentParser(
98+
formatter_class=ArgumentDefaultsHelpFormatter,
99+
)
100+
argument_parser.add_argument('--a', type=int, metavar='int', default=SUPPRESS, required=True, help='a')
101+
argument_parser.add_argument('--b', type=tp_bool, metavar='bool', default=True, help='b')
102+
argument_parser.add_argument('--c', type=str, metavar='str', default='3', help='c')
103+
argument_parser.add_argument('--d', type=float, metavar='float', default=4.0, help='d')
104+
argument_parser.add_argument('--e', type=Path, metavar='path', default=Path.home(), help='e')
105+
106+
args = argument_parser.parse_args()
107+
foo(a=args.a, b=args.b, c=args.c, d=args.d, e=args.e)
108+
```
109+
110+
Moreover, if you register more than one functions, e.g., register function `add`,
111+
112+
```python
113+
@aku.option
114+
def add(x: int, y: int):
115+
print(f'{x} + {y} => {x + y}')
116+
```
117+
118+
Then you can choose which one to run by passing its name as the first parameter,
119+
120+
```shell script
121+
~ python examples/bar.py foo --help
122+
usage: bar.py foo [-h] --a int [--b bool] [--c str] [--d float] [--e path]
123+
124+
optional arguments:
125+
-h, --help show this help message and exit
126+
--a int a
127+
--b bool b (default: True)
128+
--c str c (default: 3)
129+
--d float d (default: 4.0)
130+
--e path e (default: /Users/home)
33131

34-
```shell
35-
~ python tests/test_single_function.py --help
36-
usage: aku [-h] --a A [--b B]
132+
~ python examples/bar.py add --help
133+
usage: bar.py add [-h] --x int --y int
37134

38135
optional arguments:
39136
-h, --help show this help message and exit
40-
--a A a (default: None)
41-
--b B b (default: 2)
137+
--x int x
138+
--y int y
42139

140+
~ python examples/bar.py add --x 1 --y 2
141+
1 + 2 => 3
43142
```
44143

45-
if you registered more than one functions, then sub-parser will be utilized.
144+
### Container Types
46145

47146
```python
48-
# file test_multi_functions.py
49-
import aku
147+
from typing import List, Tuple
148+
149+
from aku import Aku, Literal
150+
151+
aku = Aku()
152+
153+
154+
@aku.option
155+
def baz(a: List[int], b: Tuple[bool, ...], c: Tuple[int, bool, str], d: Literal[42, 1905]):
156+
print(f'a => {a}')
157+
print(f'b => {b}')
158+
print(f'c => {c}')
159+
print(f'd => {d}')
160+
161+
162+
if __name__ == '__main__':
163+
aku.run()
164+
```
50165

51-
app = aku.App()
166+
* argument `a` is annotated with `List[int]`, thus every `--a` appends one item at the end of existing list
167+
* homogenous tuple holds arbitrary number of elements with the same type, while heterogeneous tuple holds specialized number of elements with specialized type
168+
* literal arguments can be assigned value from the specified ones
52169

170+
```shell script
171+
~ python examples/baz.py --help
172+
usage: baz.py [-h] --a [int] --b bool, ...) --c (int, bool, str --d int{1905, 42}
53173

54-
@app.register
55-
def add(a: int, b: int = 2):
56-
print(f'{a} + {b} => {a + b}')
174+
optional arguments:
175+
-h, --help show this help message and exit
176+
--a [int] a
177+
--b (bool, ...) b
178+
--c (int, bool, str) c
179+
--d int{1905, 42} d
180+
181+
~ python examples/baz.py --a 1 --a 2 --a 3 --b "true,true,false,false,true" --c 42,true,"yes" --d 42
182+
a => [1, 2, 3]
183+
b => (True, True, False, False, True)
184+
c => (42, True, 'yes')
185+
d => 42
57186

187+
~ python examples/baz.py --a 1 --a 2 --a nice --b "true,wow" --c 42,true,"yes" --d 42
188+
usage: baz.py [-h] [--a [int]] --b bool, ...) --c (int, bool, str --d int{1905, 42}
189+
baz.py: error: argument --a: invalid int value: 'nice'
58190

59-
@app.register
60-
def say_hello(name: str):
61-
print(f'hello {name}')
191+
~ python examples/baz.py --a 1 --a 2 --a 3 --b "true,wow" --c 42,true,"yes" --d 42
192+
usage: baz.py [-h] [--a [int]] --b bool, ...) --c (int, bool, str --d int{1905, 42}
193+
baz.py: error: argument --b: invalid fn value: 'true,wow'
62194

195+
~ python examples/baz.py --a 1 --a 2 --a 3 --b "true,true,false,false,true" --c 42,true,"yes,43" --d 42
196+
usage: baz.py [-h] [--a [int]] [--b bool, ...)] --c (int, bool, str --d int{1905, 42}
197+
baz.py: error: argument --c: invalid fn value: '42,true,yes,43'
63198

64-
app.run()
199+
~ python examples/baz.py --a 1 --a 2 --a 3 --b "true,true,false,false,true" --c 42,true,"yes" --d 43
200+
usage: baz.py [-h] [--a [int]] [--b bool, ...)] [--c (int, bool, str] --d int{1905, 42}
201+
baz.py: error: argument --d: invalid choice: 43 (choose from 42, 1905)
65202
```
66203
67-
your argument parser interface will looks like,
204+
### Nested Types
205+
206+
Wrap your function in `Type` and then this can be passed as a higher-order type to annotations, then `aku` can recursively analysis them. For `Union` type, you can choose which type to run at command line interface. To avoid name conflicting, you can open a sub-namespace by adding a underline to your argument name.
207+
208+
```python
209+
from typing import Type, Union
210+
from aku import Aku
211+
212+
213+
def add(x: int, y: int):
214+
print(f'{x} + {y} => {x + y}')
215+
68216

69-
```shell
70-
~ python tests/test_multi_functions.py --help
71-
usage: aku [-h] {add,say_hello} ...
217+
def sub(x: int, y: int):
218+
print(f'{x} - {y} => {x - y}')
72219

73-
positional arguments:
74-
{add,say_hello}
220+
221+
aku = Aku()
222+
223+
224+
@aku.option
225+
def one(op: Union[Type[add], Type[sub]]):
226+
op()
227+
228+
229+
@aku.option
230+
def both(lhs_: Type[add], rhs_: Type[sub]):
231+
lhs_()
232+
rhs_()
233+
234+
235+
if __name__ == '__main__':
236+
aku.run()
237+
```
238+
239+
```shell script
240+
~ python examples/qux.py one --op add --help
241+
usage: qux.py one [-h] [--op {add, sub}[fn]]
75242

76243
optional arguments:
77-
-h, --help show this help message and exit
244+
-h, --help show this help message and exit
245+
--op {add, sub}[fn] op (default: (<function add at 0x7fc1bc223700>, 'op'))
246+
--x int x
247+
--y int y
248+
249+
~ python examples/qux.py one --op add --x 1 --y 2
250+
1 + 2 => 3
78251

79-
~ python tests/test_multi_functions.py say_hello --help
80-
usage: aku say_hello [-h] --name NAME
252+
~ python examples/qux.py one --op sub --help
253+
usage: qux.py one [-h] [--op {add, sub}[fn]]
254+
255+
optional arguments:
256+
-h, --help show this help message and exit
257+
--op {add, sub}[fn] op (default: (<function sub at 0x7ff968a2db80>, 'op'))
258+
--x int x
259+
--y int y
260+
261+
~ python examples/qux.py one --op sub --x 1 --y 2
262+
1 - 2 => -1
263+
264+
~ python examples/qux.py both --help
265+
usage: qux.py both [-h] --lhs-x int --lhs-y int --rhs-x int --rhs-y int
81266

82267
optional arguments:
83268
-h, --help show this help message and exit
84-
--name NAME name (default: None)
269+
--lhs-x int lhs-x
270+
--lhs-y int lhs-y
271+
--rhs-x int rhs-x
272+
--rhs-y int rhs-y
85273

86-
~ python tests/test_multi_functions.py say_hello --name aku
87-
hello aku
88-
```
274+
~ python examples/qux.py both --lhs-x 1 --lhs-y 2 --rhs-x 3 --rhs-y 4
275+
1 + 2 => 3
276+
3 - 4 => -1
277+
```

0 commit comments

Comments
 (0)