Skip to content

Commit 1e60624

Browse files
committed
Adding tapify to README
1 parent ccda769 commit 1e60624

File tree

1 file changed

+177
-20
lines changed

1 file changed

+177
-20
lines changed

README.md

Lines changed: 177 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,24 @@ Tap provides the following benefits:
2222

2323
See [this poster](https://docs.google.com/presentation/d/1AirN6gpiq4P1L8K003EsXmobVxP3A4AVEIR2KOEQN7Y/edit?usp=sharing), which we presented at [PyCon 2020](https://us.pycon.org/2020/), for a presentation of some of the relevant concepts we used to guide the development of Tap.
2424

25+
As of version 1.8.0, Tap now includes `tapify`, which runs functions or initializes classes with arguments parsed from the command line. We show an example below.
26+
27+
```python
28+
# square.py
29+
from tap import tapify
30+
31+
def square(num: float) -> float:
32+
return num ** 2
33+
34+
if __name__ == '__main__':
35+
print(f'The square of your number is {tapify(square)}.')
36+
```
37+
38+
Running `python square.py --num 2` will print `The square of your number is 4.0.`. Please see [tapify](#tapify) for more details.
39+
2540
## Installation
2641

27-
Tap requires Python 3.6+
42+
Tap requires Python 3.7+
2843

2944
To install Tap from PyPI run:
3045
```
@@ -46,31 +61,26 @@ pip install -e .
4661
* [Tap is Python-native](#tap-is-python-native)
4762
* [Tap features](#tap-features)
4863
+ [Arguments](#arguments)
49-
+ [Help string](#help-string)
50-
+ [Flexibility of `configure`](#flexibility-of--configure-)
64+
+ [Tap help](#tap-help)
65+
+ [Configuring arguments](#configuring-arguments)
5166
- [Adding special argument behavior](#adding-special-argument-behavior)
5267
- [Adding subparsers](#adding-subparsers)
5368
+ [Types](#types)
54-
- [`str`, `int`, and `float`](#-str----int---and--float-)
55-
- [`bool`](#-bool-)
56-
- [`Optional`](#-optional-)
57-
- [`List`](#-list-)
58-
- [`Set`](#-set-)
59-
- [`Tuple`](#-tuple-)
60-
- [`Literal`](#-literal-)
61-
- [`Union`](#-union-)
62-
- [Complex Types](#complex-types)
63-
+ [Argument processing with `process_args`](#argument-processing-with--process-args-)
69+
+ [Argument processing](#argument-processing)
6470
+ [Processing known args](#processing-known-args)
6571
+ [Subclassing](#subclassing)
6672
+ [Printing](#printing)
6773
+ [Reproducibility](#reproducibility)
68-
- [Reproducibility info](#reproducibility-info)
6974
+ [Saving and loading arguments](#saving-and-loading-arguments)
70-
- [Save](#save)
71-
- [Load](#load)
72-
- [Load from dict](#load-from-dict)
7375
+ [Loading from configuration files](#loading-from-configuration-files)
76+
* [tapify](#tapify)
77+
+ [Examples](#examples)
78+
- [Function](#function)
79+
- [Class](#class)
80+
- [Dataclass](#dataclass)
81+
+ [tapify help](#tapify-help)
82+
+ [Command line vs explicit arguments](#command-line-vs-explicit-arguments)
83+
+ [Known args](#known-args)
7484

7585
## Tap is Python-native
7686

@@ -143,7 +153,7 @@ class MyTap(Tap):
143153
default_arg: str = 'default value'
144154
```
145155

146-
### Help string
156+
### Tap help
147157

148158
Single line and/or multiline comments which appear after the argument are automatically parsed into the help string provided when running `python main.py -h`. The type and default values of arguments are also provided in the help string.
149159

@@ -172,7 +182,7 @@ optional arguments:
172182
-h, --help show this help message and exit
173183
```
174184

175-
### Flexibility of `configure`
185+
### Configuring arguments
176186
To specify behavior beyond what can be specified using arguments as class variables, override the `configure` method.
177187
`configure` provides access to advanced argument parsing features such as `add_argument` and `add_subparser`.
178188
Since Tap is a wrapper around argparse, Tap provides all of the same functionality.
@@ -329,7 +339,7 @@ print(f'{args.aged_person.name} is {args.aged_person.age}') # Tapper is 27
329339
```
330340

331341

332-
### Argument processing with `process_args`
342+
### Argument processing
333343

334344
With complex argument parsing, arguments often end up having interdependencies. This means that it may be necessary to disallow certain combinations of arguments or to modify some arguments based on other arguments.
335345

@@ -565,3 +575,150 @@ args = Args(config_files=['my_config_shlex.txt']).parse_args()
565575
to get the resulting `args = {'arg1': 21, 'arg2': 'two three four'}`
566576

567577
The legacy parsing behavior of using standard string split can be re-enabled by passing `legacy_config_parsing=True` to `parse_args`.
578+
579+
## tapify
580+
581+
`tapify` makes it possible to run functions or initialize objects via command line arguments. This is inspired by Google's [Python Fire](https://github.com/google/python-fire), but `tapify` also automatically casts command line arguments to the appropriate types based on the type hints. Under the hood, `tapify` implicitly creates a Tap object and uses it to parse the command line arguments, which it then uses to run the function or initialize the class. We show a few examples below.
582+
583+
### Examples
584+
585+
#### Function
586+
587+
```python
588+
# square_function.py
589+
from tap import tapify
590+
591+
def square(num: float) -> float:
592+
"""Square a number.
593+
594+
:param num: The number to square.
595+
"""
596+
return num ** 2
597+
598+
if __name__ == '__main__':
599+
squared = tapify(square)
600+
print(f'The square of your number is {squared}.')
601+
```
602+
603+
Running `python square_function.py --num 5` prints `The square of your number is 25.0.`.
604+
605+
#### Class
606+
607+
```python
608+
# square_class.py
609+
from tap import tapify
610+
611+
class Squarer:
612+
def __init__(self, num: float) -> None:
613+
"""Initialize the Squarer with a number to square.
614+
615+
:param num: The number to square.
616+
"""
617+
self.num = num
618+
619+
def get_square(self) -> float:
620+
"""Get the square of the number."""
621+
return self.num ** 2
622+
623+
if __name__ == '__main__':
624+
squarer = tapify(Squarer)
625+
print(f'The square of your number is {squarer.get_square()}.')
626+
```
627+
628+
Running `python square_class.py --num 2` prints `The square of your number is 4.0.`.
629+
630+
#### Dataclass
631+
632+
```python
633+
# square_dataclass.py
634+
from dataclasses import dataclass
635+
636+
from tap import tapify
637+
638+
@dataclass
639+
class Squarer:
640+
"""Squarer with a number to square.
641+
642+
:param num: The number to square.
643+
"""
644+
num: float
645+
646+
def get_square(self) -> float:
647+
"""Get the square of the number."""
648+
return self.num ** 2
649+
650+
if __name__ == '__main__':
651+
squarer = tapify(Squarer)
652+
print(f'The square of your number is {squarer.get_square()}.')
653+
```
654+
655+
Running `python square_dataclass.py --num -1` prints `The square of your number is 1.0.`.
656+
657+
### tapify help
658+
659+
The help string on the command line is set based on the docstring for the function or class. For example, running `python square_function.py -h` will print:
660+
661+
```
662+
usage: square_function.py [-h] --num NUM
663+
664+
Square a number.
665+
666+
options:
667+
-h, --help show this help message and exit
668+
--num NUM (float, required) The number to square.
669+
```
670+
671+
Note that for classes, if there is a docstring in the `__init__` method, then `tapify` sets the help string description to that docstring. Otherwise, it uses the docstring from the top of the class.
672+
673+
### Command line vs explicit arguments
674+
675+
`tapify` can simultaneously use both arguments passed from the command line and arguments passed in explicitly in the `tapify` call. Arguments provided in the `tapify` call override function defaults, and arguments provided via the command line override both arguments provided in the `tapify` call and function defaults. We show an example below.
676+
677+
```python
678+
# add.py
679+
from tap import tapify
680+
681+
def add(num_1: float, num_2: float = 0.0, num_3: float = 0.0) -> float:
682+
"""Add numbers.
683+
684+
:param num_1: The first number.
685+
:param num_2: The second number.
686+
:param num_3: The third number.
687+
"""
688+
return num_1 + num_2 + num_3
689+
690+
if __name__ == '__main__':
691+
added = tapify(add, num_2=2.2, num_3=4.1)
692+
print(f'The sum of your numbers is {added}.')
693+
```
694+
695+
Running `python add.py --num_1 1.0 --num_2 0.9` prints `The sum of your numbers is 6.0.`. (Note that `add` took `num_1 = 1.0` and `num_2 = 0.9` from the command line and `num_3=4.1` from the `tapify` call due to the order of precedence.)
696+
697+
### Known args
698+
699+
Calling `tapify` with `known_only=True` allows `tapify` to ignore additional arguments from the command line that are not needed for the function or class. If `known_only=False` (the default), then `tapify` will raise an error when additional arguments are provided. We show an example below where `known_only=True` might be useful for running multiple `tapify` calls.
700+
701+
```python
702+
# person.py
703+
from tap import tapify
704+
705+
def print_name(name: str) -> None:
706+
"""Print a person's name.
707+
708+
:param name: A person's name.
709+
"""
710+
print(f'My name is {name}.')
711+
712+
def print_age(age: int) -> None:
713+
"""Print a person's age.
714+
715+
:param name: A person's age.
716+
"""
717+
print(f'My age is {age}.')
718+
719+
if __name__ == '__main__':
720+
tapify(print_name, known_only=True)
721+
tapify(print_age, known_only=True)
722+
```
723+
724+
Running `python person.py --name Jesse --age 1` prints `My name is Jesse.` followed by `My age is 1.`. Without `known_only=True`, the `tapify` calls would raise an error due to the extra argument.

0 commit comments

Comments
 (0)