Skip to content

Commit 8d27931

Browse files
Change the way argumentify handles function parameters to argument-parser arguments conversion.
+ `POSITIONAL_ONLY` and `VAR_POSITIONAL` parameters will be positional arguments. + `POSITIONAL_OR_KEYWORD` and `KEYWORD_ONLY` parameters have flags assigned to them. + `POSITIONAL_OR_KEYWORD` parameters will be required if at least one `KEYWORD_ONLY` parameter is there, otherwise they are optional. + `VAR_KEYWORD` parameters are still not supported.
1 parent 65cc612 commit 8d27931

13 files changed

Lines changed: 339 additions & 77 deletions

File tree

CHANGELOG.md

Lines changed: 138 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,140 @@ Help this project by [Donation](DONATE.md)
66
Changes
77
-------
88

9+
### v3.1.0
10+
11+
Change the way `argumentify` handles function parameters to argument-parser arguments
12+
conversion.
13+
14+
- `POSITIONAL_ONLY` and `VAR_POSITIONAL` parameters will be positional arguments.
15+
- `POSITIONAL_OR_KEYWORD` and `KEYWORD_ONLY` parameters have flags assigned to them.
16+
- `POSITIONAL_OR_KEYWORD` parameters will be required if at least one `KEYWORD_ONLY`
17+
parameter is there, otherwise they are optional.
18+
- `VAR_KEYWORD` parameters are still not supported.
19+
20+
#### Example 1
21+
22+
```python
23+
def main(path: Path, /, output: Path, *, verbose: bool = False):
24+
"""Process a file.
25+
26+
:param path: The input file path
27+
:param output: The output file
28+
:param verbose: Write more logs to the standard output.
29+
"""
30+
...
31+
32+
33+
if __name__ == "__main__":
34+
argumentify(main)
35+
```
36+
37+
The help looks like this:
38+
39+
```help
40+
usage: test.py [-h] --output OUTPUT [--verbose] path
41+
42+
Process a file.
43+
44+
positional arguments:
45+
path The input file path
46+
47+
options:
48+
-h, --help
49+
show this help message and exit
50+
--output OUTPUT, -o OUTPUT
51+
The output file
52+
--verbose, -v
53+
Write more logs to the standard output.
54+
55+
```
56+
57+
_Note that `path` and `output` are required._
58+
59+
#### Example 2
60+
61+
```python
62+
def main(output: Path, /, *inputs: Path):
63+
"""Process multiple files into one.
64+
65+
:param output: The output file
66+
:param inputs: The path to the input files
67+
"""
68+
# Since `inputs` is a VAR_POSITIONAL, while being a positional argument, it can have
69+
# zero length which is in many cases not intended.
70+
# You might want to add a check for its length and raise an ArgumentError if it does
71+
# not match your needs
72+
73+
# Check if at least one input has been passed and mark the argument as required
74+
# if len(inputs) < 1:
75+
# raise RequiredArgumentError("inputs")
76+
77+
# Raise an error unless at least two inputs are present
78+
if len(inputs) < 2:
79+
raise ArgumentError(message="You need to pass at least two files as input.")
80+
...
81+
```
82+
83+
The help looks like this:
84+
85+
```help
86+
usage: test.py [-h] output [inputs ...]
87+
88+
Process multiple files into one.
89+
90+
positional arguments:
91+
output The output file
92+
inputs The path to the input files
93+
94+
options:
95+
-h, --help
96+
show this help message and exit
97+
98+
```
99+
100+
#### Example 3
101+
102+
```python
103+
def main(first_name: str, last_name: str, output: Path, verbose: bool = False):
104+
"""Write a greeting message.
105+
106+
:param first_name: The first name of the user to greet (optional)
107+
:param last_name: The last name of the user to greet (optional)
108+
:param output: The output file (stdout if none is provided)
109+
:param verbose: If provided, will write the debug logs to stdout
110+
"""
111+
...
112+
113+
114+
if __name__ == "__main__":
115+
argumentify(main)
116+
```
117+
118+
The help looks like this:
119+
120+
```help
121+
usage: test.py [-h] [--first-name FIRST_NAME] [--last-name LAST_NAME] [--output OUTPUT]
122+
[--verbose]
123+
124+
Write a greeting message.
125+
126+
options:
127+
-h, --help
128+
show this help message and exit
129+
--first-name FIRST_NAME, -f FIRST_NAME
130+
The first name of the user to greet (optional)
131+
--last-name LAST_NAME, -l LAST_NAME
132+
The last name of the user to greet (optional)
133+
--output OUTPUT, -o OUTPUT
134+
The output file (stdout if none is provided)
135+
--verbose, -v
136+
If provided, will write the debug logs to stdout
137+
138+
```
139+
140+
_Note that all the options are optional and default to None. `verbose` is False by
141+
default since a default value is provided for it in function definition._
142+
9143
### v3.0.2
10144

11145
Change `argumentify` to use the whole function description as the argument-parser
@@ -46,7 +180,7 @@ Now at v3.0.2:
46180
```help
47181
usage: test.py [-h] [--verbose]
48182
49-
This is a very useful tool and I will describe it thoroughly. It is so good that we have a
183+
This is a very useful tool and I will describe it thoroughly. It is so good that we have
50184
second line in the first part of the description. And now we can talk more about the tool...
51185
52186
options:
@@ -155,7 +289,7 @@ for detailed upgrade instructions.
155289
### 2.10.0
156290

157291
- Added some exception classes to raise in the "argumentified" functions to show
158-
*parser error* to the user: `ArgumentError`, `IncompatibleArguments`,
292+
**parser error** to the user: `ArgumentError`, `IncompatibleArguments`,
159293
`RequiredArgument`, `TooFewArguments`
160294

161295
### 2.9.2
@@ -373,7 +507,7 @@ clears the current line in the console and moves the cursor to the beginning of
373507
### 2.3.3
374508

375509
Fixed a bug that would cause an error creating a progress bar with no value set for
376-
width in systems without support for os.get_terminal_size().
510+
width in systems without support for `os.get_terminal_size()`.
377511

378512
### 2.3.2
379513

@@ -560,7 +694,7 @@ TypeError: A logger name must be a string
560694
You can use `Logger.print` to print a message using the current level of the logger
561695
class.
562696

563-
*It gets printed with any level.*
697+
_It gets printed with any level._
564698

565699
### 1.4.6
566700

EXAMPLES.md

Lines changed: 34 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -211,22 +211,25 @@ import log21
211211

212212

213213
class ReversedText:
214-
def __init__(self, text: str):
214+
215+
def __init__(self, text: str) -> None:
215216
self._text = text[::-1]
216217

217-
def __str__(self):
218+
def __str__(self) -> str:
218219
return self._text
219220

220-
def __repr__(self):
221+
def __repr__(self) -> str:
221222
return f"<{self.__class__.__name__}(text='{self._text}') at {hex(id(self))}>"
222223

223224

224225
# Old way
225226
def main():
226-
"""Here is my main function"""
227+
"""Here is my main function."""
227228
parser = log21.ColorizingArgumentParser()
228-
parser.add_argument('--positional-arg', '-p', action='store', type=int,
229-
required=True, help="This argument is positional!")
229+
parser.add_argument('positional_arg', action='store', type=int,
230+
help="This argument is positional!")
231+
parser.add_argument('--required-arg', '-r', action='store', type=float,
232+
help="You have to pass this argument!")
230233
parser.add_argument('--optional-arg', '-o', action='store', type=ReversedText,
231234
help="Whatever you pass here will be REVERSED!")
232235
parser.add_argument('--arg-with-default', '-a', action='store', default=21,
@@ -241,6 +244,7 @@ def main():
241244
log21.basic_config(level='DEBUG')
242245

243246
log21.info(f"positional_arg = {args.positional_arg}")
247+
log21.info(f"required_arg = {args.required_arg}")
244248
log21.info(f"optional_arg = {args.optional_arg}")
245249
log21.debug(f"arg_with_default = {args.arg_with_default}")
246250
log21.debug(f"additional_arg = {args.additional_arg}")
@@ -251,11 +255,20 @@ if __name__ == '__main__':
251255

252256

253257
# New way
254-
def main(positional_arg: int, /, optional_arg: ReversedText, arg_with_default: int = 21,
255-
additional_arg=None, verbose: bool = False):
256-
"""Some description
258+
def main(
259+
positional_arg: int,
260+
/,
261+
required_arg: float,
262+
*,
263+
optional_arg: ReversedText,
264+
arg_with_default: int = 21,
265+
additional_arg=None,
266+
verbose: bool = False
267+
):
268+
"""Some description.
257269
258270
:param positional_arg: This argument is positional!
271+
:param required_arg: You have to pass this argument!
259272
:param optional_arg: Whatever you pass here will be REVERSED!
260273
:param arg_with_default: The default value is 21
261274
:param additional_arg: This one is extra.
@@ -265,6 +278,7 @@ def main(positional_arg: int, /, optional_arg: ReversedText, arg_with_default: i
265278
log21.basic_config(level='DEBUG')
266279

267280
log21.info(f"{positional_arg = }")
281+
log21.info(f"{required_arg = }")
268282
log21.info(f"{optional_arg = !s}")
269283
log21.debug(f"{arg_with_default = }")
270284
log21.debug(f"{additional_arg = !s}")
@@ -282,6 +296,7 @@ Example with multiple functions as entry-point:
282296
```python
283297
import ast
284298
import operator
299+
import contextlib
285300
from functools import reduce
286301

287302
import log21
@@ -349,8 +364,9 @@ def addition(*numbers: float):
349364
numbers (float): numbers to add
350365
"""
351366
if len(numbers) < 2:
352-
log21.error('At least two numbers are required! Use `-n`.')
353-
return
367+
raise log21.ArgumentError(
368+
message='At least two numbers are required!'
369+
)
354370
log21.info(f'Result: {sum(numbers)}')
355371

356372

@@ -361,8 +377,9 @@ def multiplication(*numbers: float):
361377
numbers (float): numbers to multiply
362378
"""
363379
if len(numbers) < 2:
364-
log21.error('At least two numbers are required! Use `-n`.')
365-
return
380+
raise log21.ArgumentError(
381+
message='At least two numbers are required!'
382+
)
366383
log21.info(f'Result: {reduce(lambda x, y: x * y, numbers)}')
367384

368385

@@ -374,17 +391,16 @@ def calc(*inputs: str, verbose: bool = False):
374391
expression = ' '.join(inputs)
375392

376393
if len(expression) < 3:
377-
log21.error('At least two numbers and one operator are required! Use `-i`.')
378-
return
394+
raise log21.ArgumentError(
395+
message='At least two numbers and one operator are required!'
396+
)
379397

380398
if verbose:
381399
log21.basic_config(level='DEBUG')
382400

383401
log21.debug(f'Expression: {expression}')
384-
try:
402+
with contextlib.suppress(TypeError, KeyError, SyntaxError):
385403
log21.info(f'Result: {safe_eval(expression)}')
386-
except (TypeError, KeyError, SyntaxError):
387-
pass
388404

389405

390406
if __name__ == "__main__":
@@ -396,7 +412,6 @@ if __name__ == "__main__":
396412
Example with parser errors:
397413

398414
```python
399-
# Common Section
400415
import log21
401416

402417

@@ -411,7 +426,6 @@ class ReversedText:
411426
return f"<{self.__class__.__name__}(text='{self._text}') at {hex(id(self))}>"
412427

413428

414-
# New way
415429
def main(positional_arg: int, /, optional_arg: ReversedText, arg_with_default: int = 21,
416430
additional_arg=None, verbose: bool = False, quiet: bool = False):
417431
"""Some description

0 commit comments

Comments
 (0)