Turn annotated functions into command line scripts with a single line of code, and keep all your documentation!
Uses type annotations on functions to parse command line arguments, and strips help strings from function documentation.
Say we have a file say_hello.py
that looks like:
def say_hello(name: str, friendliness: int):
"""Say hello with various degrees of friendliness
Args:
name:
The name of the person to greet
friendliness:
The level of friendliness to greet them with
"""
if friendliness == 0:
print("hey.")
elif friendliness == 1:
print(f"Hi {name}")
elif friendliness > 1:
print(f"Hello {name}!")
else:
raise ValueError(
"Friendliness level cannot be less than 0"
)
This function can be run as a command line utility by just adding
from hermes.typeo import typeo
@typeo
def say_hello(name: str, friendliness: int = 1):
"""Say hello to someone with various degrees of friendliness
Args:
name:
The name of the person to greet
friendliness:
The level of friendliness to greet them with
"""
if friendliness == 0:
print("hey.")
elif friendliness == 1:
print(f"Hi {name}")
elif friendliness > 1:
print(f"Hello {name}!")
else:
raise ValueError(
"Friendliness level cannot be less than 0"
)
if __name__ == "__main__":
say_hello()
Now when we run from the command line:
$ python say_hello.py -h
usage: say_hello [-h] --name NAME [--friendliness FRIENDLINESS]
Say hello to someone with various degrees of friendliness
optional arguments:
-h, --help show this help message and exit
--name NAME The name of the person to greet (default: None)
--friendliness FRIENDLINESS
The level of friendliness to greet them with (default: 1)
$ python say_hello.py --name Thom
Hi Thom
$ python say_hello.py --name Thom --friendliness 2
Hello Thom!
$ python say_hello.py --friendliness 0
usage: say_hello [-h] --name NAME [--friendliness FRIENDLINESS]
say_hello: error: the following arguments are required: --name
Note that we can still import say_hello
in other scripts and call it with regular arguments, and its behavior won't be impacted. typeo
works by only reading from the command line if no arguments are passed in.
from say_hello import say_hello
# prints "Hi Thom"
say_hello("Thom", 1)
Note also that we can drop the if __name__ == "__main__"
syntax if we integrate with Poetry package scripts. For example, if my pyproject.toml
in the directory where I host say_hello.py
has a section like:
[tool.poetry.scripts]
greet = "say_hello:say_hello"
Then I can drop the if __name__ == "__main__"
from say_hello.py
and run my script like this
$ poetry run greet --name Thom
Hi Thom
We can also add subcommands to our scripts. Let's say greet.py
looks like
from hermes.typeo import typeo
def validate_friendliness(friendliness: int):
if friendliness < 0:
raise ValueError(
"Friendliness level cannot be less than 0"
)
def say_goodbye(name: str, friendliness: int = 1):
"""Say goodbye to someone with various degrees of friendliness
Args:
name:
The name of the person to bid farewell
friendliness:
The level of friendliness to bid them farewell with
"""
validate_friendliness(friendliness)
if friendliness == 0:
print("bye.")
elif friendliness == 1:
print(f"Goodbye {name}")
else:
print(f"So long {name}!")
def say_hello(name: str, friendliness: int = 1):
"""Say hello to someone with various degrees of friendliness
Args:
name:
The name of the person to greet
friendliness:
The level of friendliness to greet them with
"""
validate_friendliness(friendliness)
if friendliness == 0:
print("hey.")
elif friendliness == 1:
print(f"Hi {name}")
else:
print(f"Hello {name}!")
@typeo(hello=say_hello, goodbye=say_goodbye)
def greet(greeter: str):
print(f"This is a greeting from {greeter}:")
if __name__ == "__main__":
greet()
$ python greet.py -h
usage: greet [-h] --greeter GREETER {hello,goodbye} ...
positional arguments:
{hello,goodbye}
optional arguments:
-h, --help show this help message and exit
--greeter GREETER
$ python greet.py hello -h
usage: greet hello [-h] --name NAME [--friendliness FRIENDLINESS]
Say hello to someone with various degrees of friendliness
optional arguments:
-h, --help show this help message and exit
--name NAME The name of the person to greet (default: None)
--friendliness FRIENDLINESS
The level of friendliness to greet them with (default: 1)
$ python greet.py goodbye -h
usage: greet goodbye [-h] --name NAME [--friendliness FRIENDLINESS]
Say goodbye to someone with various degrees of friendliness
optional arguments:
-h, --help show this help message and exit
--name NAME The name of the person to bid farewell (default: None)
--friendliness FRIENDLINESS
The level of friendliness to bid them farewell with (default: 1)
$ python greet.py --greeter Jonny hello --name Thom
This is a greeting from Jonny:
Hi Thom
$ python greet.py --greeter Phil goodbye --name Jonny --friendliness 2
This is a greeting from Phil:
So long Jonny!