Command line arguments, with types
Project description
argtyped
: Command Line Argument Parser, with Types
argtyped
is an command line argument parser with that relies on type annotations. It is built on
argparse
, the command line argument parser library built into
Python. Compared with argparse
, this library gives you:
- More concise and intuitive syntax, less boilerplate code.
- Type checking and IDE auto-completion for command line arguments.
- A drop-in replacement for
argparse
in most cases.
Installation
Install stable release from PyPI:
pip install argtyped
Or, install the latest commit from GitHub:
pip install -e git+https://github.com/huzecong/argtyped.git
Usage
With argtyped
, you can define command line arguments in a syntax similar to
typing.NamedTuple
. The syntax is intuitive and can
be illustrated with an example:
from typing import Optional
from argtyped import Arguments, Choices, Switch
from argtyped import Enum, auto
class LoggingLevels(Enum):
Debug = auto()
Info = auto()
Warning = auto()
Error = auto()
Critical = auto()
class MyArguments(Arguments):
model_name: str # required argument of `str` type
hidden_size: int = 512 # `int` argument with default value of 512
activation: Choices['relu', 'tanh', 'sigmoid'] = 'relu' # argument with limited choices
logging_level: LoggingLevels = LoggingLevels.Info # using `Enum` class as choices
use_dropout: Switch = True # switch argument, enable with "--use-dropout" and disable with "--no-use-dropout"
dropout_prob: Optional[float] = 0.5 # optional argument, "--dropout-prob=none" parses into `None`
args = Arguments()
This is equivalent to the following code with Python built-in argparse
:
import argparse
from enum import Enum
class LoggingLevels(Enum):
Debug = "debug"
Info = "info"
Warning = "warning"
Error = "error"
Critical = "critical"
parser = argparse.ArgumentParser()
parser.add_argument("--model-name", type=str, required=True)
parser.add_argument("--hidden-size", type=int, default=512)
parser.add_argument("--activation", choices=["relu", "tanh", "sigmoid"], default="relu")
parser.add_argument("--logging-level", choices=list(LoggingLevels), type=LoggingLevels, default="info")
parser.add_argument("--use-dropout", action="store_true", dest="use_dropout", default=True)
parser.add_argument("--no-use-dropout", action="store_false", dest="use_dropout")
parser.add_argument("--dropout-prob", type=lambda s: None if s.lower() == 'none' else float(s), default=0.5)
args = parser.parse_args()
Save the code into a file named main.py
. Suppose the following arguments are provided:
python main.py \
--model-name LSTM \
--activation sigmoid \
--logging-level debug \
--no-use-dropout \
--dropout-prob none
Then the parsed arguments will be equivalent to the following structured returned by argparse
:
argparse.Namespace(
model_name="LSTM", hidden_size=512, activation="sigmoid", logging_level="debug",
use_dropout=False, dropout_prob=None)
Arguments can also be pretty-printed with print(args.to_string())
, which gives:
<class '__main__.MyArguments'>
╔═════════════════╤══════════════════════════════════╗
║ Arguments │ Values ║
╠═════════════════╪══════════════════════════════════╣
║ model_name │ 'LSTM' ║
║ hidden_size │ 512 ║
║ activation │ 'sigmoid' ║
║ logging_level │ <MyLoggingLevels.Debug: 'debug'> ║
║ use_dropout │ False ║
║ dropout_prob │ None ║
║ label_smoothing │ 0.1 ║
║ some_true_arg │ True ║
║ some_false_arg │ False ║
╚═════════════════╧══════════════════════════════════╝
Reference
The argtyped.Arguments
Class
The argtyped.Arguments
class is main class of the package, from which you should derive your custom class that holds
arguments. Each argument takes the form of a class attribute, with its type annotation and an optional default value.
When an instance of your custom class is initialized, the command line arguments are parsed from sys.argv
into values
with your annotated types. You can also provide the list of strings to parse by passing them as the parameter.
The parsed arguments are stored in an object of your custom type. This gives you arguments that can be auto-completed
by the IDE, and type-checked by a static type checker like mypy
.
The following example illustrates the keypoints:
class MyArgs(argtyped.Arguments):
# name: type [= default_val]
value: int = 0
args = MyArgs() # equivalent to `parser.parse_args()`
args = MyArgs(["--value", "123"]) # equivalent to `parser.parse_args(["--value", "123"])
assert isinstance(args, MyArgs)
Argument Types
To summarize, whatever works for argparse
works here. The following types are supported:
- Built-in types such as
int
,float
,str
. bool
type. Accepted values (case-insensitive) forTrue
are:y
,yes
,true
,ok
; accepted values forFalse
are:n
,no
,false
.- Choice types
Choices[...]
. A choice argument is essentially anstr
argument with limited choice of values. The ellipses can be filled with a tuple ofstr
s, or an expression that evaluates to a list ofstr
s:from argtyped import Arguments, Choices from typing import List def logging_levels() -> List[str]: return ["debug", "info", "warning", "error"] class MyArgs(Arguments): foo: Choices["debug", "info", "warning", "error"] # 4 choices bar: Choices[logging_levels()] # the same 4 choices # argv: ["--foo=debug", "--bar=info"] => foo="debug", bar="info"
This is equivalent to thechoices
keyword inargparse.add_argument
. - Enum types derived from
enum.Enum
. It is recommended to useargtyped.Enum
which uses the instance names as values:from argtyped import Enum class MyEnum(Enum): Debug = auto() # "debug" Info = auto() # "info" Warning = auto() # "warning"
- Switch types
Switch
.Switch
arguments are likebool
arguments, but they don't take values. Instead, a switch argumentswitch
requires--switch
to enable and--no-switch
to disable:from argtyped import Arguments, Switch class MyArgs(Arguments): switch: Switch = True bool_arg: bool = False # argv: [] => flag=True, bool_arg=False # argv: ["--switch", "--bool-arg=false"] => flag=True, bool_arg=False # argv: ["--no-switch", "--bool-arg=true"] => flag=False, bool_arg=True # argv: ["--switch=false"] => WRONG # argv: ["--no-bool-arg"] => WRONG
- Optional types
Optional[T]
, whereT
is any supported type (except choices or switch). An optional argument will be filled withNone
if no value is provided. It could also be explicitly set toNone
by usingnone
as value in the command line:from argtyped import Arguments from typing import Optional class MyArgs(Arguments): opt_arg: Optional[int] # implicitly defaults to `None` # argv: [] => opt_arg=None # argv: ["--opt-arg=1"] => opt_arg=1 # argv: ["--opt-arg=none"] => opt_arg=None
- Any other type that takes a single
str
as__init__
parameters. It is also theoretically possible to use a function that takes anstr
as input, but it's not recommended as it's not type-safe.
Caveats
- Advanced
argparse
features such as subparsers, groups, argument lists, and custom actions are not supported. - Using switch arguments may result in name clashes: if a switch argument has name
arg
, there can be no argument with the nameno_arg
. Optional
cannot be used withChoices
. You can add"none"
as a valid choice to mimic a similar behavior.Optional[str]
would parse a value of"none"
intoNone
.
Under the Hood
This is what happens under the hood:
- When an instance of
argtyped.Arguments
(or any subclass) is initialized, type annotations and class-level attributes (i.e., the default values) are collected to form argument declarations. - After verifying the validity of declared arguments, an instance of
argparse.ArgumentParser
is created and arguments are registered with the parser. - The parser's
parse_args
method is invoked with eithersys.argv
or strings provided as parameters, returning parsed arguments. - The parsed arguments are assigned to
self
(the instance ofArguments
subclass being initialized).
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.