A minimal library to make your option-parsing easier.
Project description
Lethargy: Terse & tiny command-line option library
Lethargy was born out of frustration, like most of my projects. It gets out of your way as soon as possible to let you get on with the actual logic. No bullshit, no magic, no objects to understand, you just call a function.
I write a lot of small scripts to get my job done faster, and manually working with options is a pain. Existing libraries are extremely verbose or just don't feel good to use. Lethargy is designed to make writing scripts easier and faster, and to reduce effort to maintain them.
- No boilerplate. Headaches are directly proportional to lines of code.
- No bloat. Small API surface area, very little to learn.
- No ambiguity. Lethargy raises exceptions instead of getting your code into bad state.
- Clear errors. Great error messages and context managers for dealing with them.
- Flexible. You're not locked in to any styles or paradigms.
Lethargy is completely imperative and is not a framework. If you are building a complete CLI or want automatic help commands, you're better off using Click — a fantastic, declarative CLI framework.
Installation
You can use pip to install lethargy. It's tiny and only depends on the standard library.
pip install lethargy
Usage
import lethargy
# Accepts the option '--bytes <int>'. Show the error nicely if it goes wrong.
with lethargy.show_errors():
n_bytes = lethargy.take_opt('bytes', 1, int) or 8
# Now the option and value have been removed from lethargy.argv
with lethargy.expect(IndexError, reason="Missing required argument: [DIR]"):
directory = lethargy.argv[1]
...
Getting Started
This is both a tutorial and the documentation. All examples assume you've got import lethargy
at the top.
FLAGS
Options can be flags. True
if present, False
if not.
# --debug
debug = lethargy.take_opt('debug')
print(debug)
$ python example.py --debug
True
$ python example.py
False
NAMES
Options can have more than one name. Instead of a string, use a list of strings. Names are case-sensitive.
# -v|--verbose
verbose = lethargy.take_opt(['v', 'verbose'])
print(verbose)
$ python example.py -v
True
$ python example.py --verbose
True
Learn more about option names
Option names are automatically generated. "use headers"
becomes --use-headers
, and "I"
becomes -I
.
If you provide an explicit name (starting with a non-alphanumeric character, such as -
, /
or +
), the name is stripped and treated as literal.
# -Enable
enabled = lethargy.take_opt('-Enable')
print(enabled)
$ python example.py -Enable
True
$ python example.py
False
Names are always case sensitive. -Enable
≠ -enable
$ python example.py -enable
False
ARGUMENTS
Options can take arguments, too. They can take any amount.
# -o|--output <value>
output = lethargy.take_opt(['o', 'output'], 1)
print(output)
$ python example.py -o out.txt
out.txt
$ python example.py
None
Learn more about arguments
If there are fewer values for the option than the number given, lethargy.ArgsError
will be raised. See Error Handling for how to present this nicely.
$ python example.py --output
Traceback (most recent call last):
[...]
lethargy.errors.ArgsError: expected 1 argument for '-o|--output <value>', found none
GREEDINESS
Options can be variadic (greedy). Use ...
instead of a number to take every value following the option.
# -i|--ignore [value]...
ignored = lethargy.take_opt(['i', 'ignore'], ...)
for pattern in ignored:
print(pattern)
$ python example.py --ignore .git .vscode .DS_Store
.git
.vscode
.DS_Store
$ python example.py --ignore experiments
experiments
$ python example.py
$ ▏
Learn more about variadic options
Because variadic options will take every argument, including values that look like other options, you should try and take these last (after taking the fixed-count options).
$ python example.py --ignore "*.pyc" --exceptions some.pyc
*.pyc
--exceptions
some.pyc
UNPACKING
Unpack multiple values into separate variables. If the option wasn't present, they'll all be None
.
# --name <value> <value> <value>
first, middle, last = lethargy.take_opt('name', 3)
print(f'Hi, {first}!')
$ python example.py --name Dwight Kurt Schrute
Hi, Dwight!
$ python example.py
Hi, None!
DEFAULTS
Set sensible defaults. Use the or
keyword and your default value(s).
# -h|--set-hours <value> <value>
start, finish = lethargy.take_opt(['set hours', 'h'], 2) or "9AM", "5PM"
print(f'Employee now works {start} to {finish}')
$ python example.py
Employee works 9AM to 5PM
$ python example.py --set-hours 8AM 4PM
Employee works 8AM to 4PM
TYPES & CONVERSION
Convert your option's values. Use a function or type as the final argument. Defaults aren't converted.
# --date-ymd <int> <int> <int>
y, m, d = lethargy.take_opt('date ymd', 3, int) or 1970, 1, 1
from datetime import datetime
date = datetime(y, m, d)
delta = datetime.today() - date
print(f'it has been {delta.days} days since {date}')
$ python example.py --date-ymd 1999 10 9
it has been 7500 days since 1999-10-09 00:00:00
ERROR HANDLING
Give clear error messages. Lucky for you, lethargy's errors are extremely descriptive.
with lethargy.show_errors():
n_bytes = lethargy.take_opt('bytes', 1, int) or 8
start, end = lethargy.take_opt(['r', 'range'], 2, int) or 0, 10
$ python example.py --range 20
Expected 2 arguments for option '-r|--range <int> <int>', but found 1 ('20')
$ python example.py --bytes
Expected 1 argument for option '--bytes <int>', but found none
$ python example.py --bytes wrong
Option '--bytes <int>' received an invalid value: 'wrong'
Learn more about error handling
Calling fail()
will exit with status code 1. You can optionally use a message.
Lethargy provides two context managers for easier error handling. These share similar behaviour, but are separate to make intent clearer.
with
lethargy.expect(*errors: Exception, reason: Optional[str] = None)
When one of the given exceptions is raised, it calls fail()
to exit and print the message.
with
lethargy.show_errors()
Same behaviour as expect
, but specifically for exceptions from lethargy.
Note that exceptions raised during value conversions will be caught by show_errors()
.
You can access the original exception that caused a TransformError
with the __cause__
attribute (see the Python Built-in Exceptions docs).
Contributing
Any and all contributions are absolutely welcome. Feel free to open an issue or just jump straight to a PR. Let's discuss and make this the best it can be! 😄
License
Lethargy is released under the MIT license.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.