Welcome to dryparse’s documentation!
Object model
The decorators are useful for commands that are simple and follow basic CLI conventions. Sometimes you want to extend your CLI with custom features. This is where the object model comes in.
Each concept in a commandline program is represented by an object in the
dryparse.objects
module. Thus, each command is represented by an
instance of Command
, each option by an
Option
, etc.
The fundamentals
The simplest way to create a command is:
git = Command("git", desc="A version control software")
Adding options is super easy:
git.paginate = Option("-p", "--paginate")
Note
The git.paginate
attribute didn’t exist before. We created it dynamically by assigning a value to it.
By default, options have a type of bool
. This means that when the option is
specified on the command line it will have a value of True
, and False
otherwise.
Let’s create an option of type int
:
git.retries = Option("-r", "--retries", type=int)
This option will expect an argument to be specified via the command line. The
argument is automatically converted to the type we specified (in this case
int
).
Note
All options except for bool
and some special types
take CLI arguments, and those arguments are automatically converted to the
specified type.
Note that commands include a --help
option by default, via a help
attribute that is just like any other attribute. You can delete it if you don’t need it:
del git.help
Adding a subcommand
Adding a subcommand is just as easy as adding an option:
git.commit = Command("commit", desc="Record changes to the repository")
git.checkout = Command("checkout", desc="Switch branches or restore working tree files")
Defining positional arguments
git.add.args = Arguments([])
These use cases are simple, but Arguments
has so much
more to offer. Take a look at its API documentation.
Root command
CLI programs usually include a --version
option in their root command. While
you can add this option yourself, we provide RootCommand
as a convenience:
git = RootCommand("git", version="0.1.0", desc="A version control software")
Customizing help
Dryparse generates standard help out of the box, but it also provides a
hierarchical representation of a help message via Help
.
You can obtain a help object for any of: Command
,
Option
, Group
.
Todo
Group help is not implemented yet.
git_help = Meta(git).help
dryparse.parse()
For example, the default help message for the git
subcommand we’ve been
building so far would be:
A version control software
Usage: git [-h] []
You can easily convert this to a help message string using:
str(git_help)
# or
git_help.text
API
dryparse.help
Help module for dryparse objects.
- class Help
Hierarchical representation of a help message. You can customize every individual part or subpart of it, or its entirety.
- text: str
The entire help text.
Overriding this property makes all the other properties obsolete.
- class CommandHelp(command: Command)
Object that represents a command’s help message organized as a hierarchy.
- signature: str
Describes the command signature when listed as a subcommand in the help message of another command.
Defaults to the command name.
- desc: dryparse.help.CommandDescription
Command description.
You can assign this to be a
str
orCommandDescription
, but you will always get aCommandDescription
when you try to access this as an attribute.
- sections: dryparse.help.HelpSectionList
Sections of this help message.
Default implementation returns a standard command section list including “usage”, “subcommands”, etc.
- section_separator: str
Separator between sections of this help message.
- listing: dryparse.help.HelpEntry
Text that appears when this subcommand is listed as a subcommand in the help message of another command.
- text: str
- class OptionHelp(option: Option)
Attributes
- override_class: Optional[Type[OptionHelp]]
When instantiating an object of type
OptionHelp
, return an instance ofoverride_class
instead.
- desc: str
Option description.
- argname: str
Name of the argument to this option.
- signature: str
Option signature.
Default value:
-o ARGNAME, --option ARGNAME
, if the option takes an argument-o ARGNAME
, if the option only has a short text--option ARGNAME
, if the option only has a long text--o ARGNAME
, if the option only has a short textARGNAME
is omitted if the option takes no argument
- hint: str
Hint for the option that appears in the “usage” section of a command.
Default value:
[-o ARGNAME]
, if the option has a short text[--option ARGNAME]
, if the option has no short textARGNAME
is omitted if the option does not take an argument
- text: str
- class HelpSection(name_or_group: Union[str, Group])
Help section, with a headline and content.
- name: str
Section name.
- group: Optional[dryparse.objects.Group]
Group
that this section refers to.
- headline: str
Section headline.
Default value:
f"{self.name}:"
.
- content: str
Section content, excluding headline.
- indent: int
Indent for the content of this section.
- active: bool
Controls whether the section is displayed.
Default value:
True
- text: str
Entire help text.
- class HelpSectionList(iterable=(), /)
A glorified list of help sections.
Behaves exactly like a normal list, with the addition that you can also access individual help sections as attributes, not just by indexing. Sections added using regular list functions can only be accessed by index (and iterators, etc.), while sections added by attribute assignment can also be accessed as attributes. In the latter case, the index is determined by the number of sections that existed before the section was added.
Examples
>>> sections = HelpSectionList() >>> # Create unnamed section at index 0 >>> sections.append(HelpSection("Usage")) >>> # Create section named usage - index automatically set to be 1 >>> sections.commands = HelpSection("Commands") >>> print(sections[0].text) Usage: >>> print(sections.usage.text) Commands: >>> print(sections[1]) Commands:
- class HelpEntry(signature, desc)
Represents an option or subcommand entry in a help message.
- signature_width: int
Width of signature after padding.
Default: 32 characters.
- padded_signature: str
Option signature padded until
signature_width
.
- text: str
Entire help text for this entry.
- class CommandDescription(long, brief=None)
Command description that can hold both a long and a brief version.
Attributes
- long: str
Long description. Used in the help text of the command at hand.
- brief: str
Brief description. Describes this command when it appears as a subcommand in another command’s help text. Falls back to
long
.
dryparse.objects
Object model of a command line program.
Option
- class Option(short: str = '', long: str = '', argname: ~typing.Optional[str] = None, default=None, argtype: type = <class 'bool'>, desc: ~typing.Optional[str] = None)
- Parameters
short (str) – Regex pattern that the short version of this option should match against. Usually this is a hyphen followed by a single letter (e.g.
-s
).long (str) – Regex pattern that the long version of this option should match against. Usually this is two hyphens followed by multiple letters. (e.g.
--long
)
Attributes
- help: OptionHelp
Customizable help object.
- build(option: Optional[str] = None)
Each time this option is specified on the command line, this method is called. The positional arguments in the function signature determine what formats are acceptable for the option.
The build function can be assigned by
option.build =
.- Parameters
option – The exact way the option was specified. This is useful when the option text is specified as a regex, or when you want to know if the option was specified using its short or long format.
Examples
Assume that the long version of the option is
--option
.If the signature is
build(self, **kwargs)
, the option is abool
option and can be specified as--option
.If the signature is
build(self, arg, **kwargs)
, the option can be specified as--option ARG
Command
- class Command(name, regex=None, desc: Optional[str] = None)
A CLI command.
You can assign arbitrary attributes dynamically. Only attributes of types
Option
,Command
,Group
and others fromdryparse.objects
have special meaning to the parser.Examples
>>> docker = Command("docker") >>> docker.context = Option("-c", "--context") >>> docker.run = Command("run", desc="Run a command in a new container")
- __call__(*args, help=None, **kwargs)
Execute the command. Unless overridden, this will process special options like
help
, and handle subcommands.
RootCommand
- class RootCommand(name, regex=None, desc='', version='0.0.0')
Command that corresponds to the program itself.
- Parameters
version (str) – Version of the program that is printed when the
--version
option is given.
Arguments
- class Arguments(*pattern: Union[type, Tuple[type, Union[int, ellipsis, range]]])
Specification for positional arguments of a command.
Positional arguments are specified on the command line as regular strings, but usually we want to restrict the number of allowed arguments, their data types, add custom validation, etc. An instance of
Arguments
holds a pattern of acceptable argument types. The arguments are converted (and effectively validated) usingconvert()
.Attributes
- pattern
Determines the number of arguments and their types. Each item of this list represents an argument or multiple arguments of a given type. The order of arguments is important.
There are two acceptable formats for each entry:
type: Accepts a single argument of the given type.
(type, number): Accepts number arguments of type type.
Note that number can be an
int
,...
(ellipsis) orrange
. Anint
specifies a fixed number of required arguments. Ellipsis is a special value meaning zero or more arguments. A range specifies a range of acceptable argument numbers.- values
The list of argument values held by this instance. This attribute is assigned when you call
assign()
, and will beNone
until then.
Notes
(type, ...)
- and(type, range)
- style patterns cannot be followed by further patterns. Instead, you should implement a custom converter function.For boolean types, you might want to use
Bool
instead ofbool
, because of potentially undesired behaviors likebool("false") == True
, etc.
Examples
Here’s an exhaustive list of example use cases:
>>> # Single argument of type int >>> Arguments(int) >>> # Two arguments of type bool >>> Arguments((bool, 2)) >>> # Single int and two bools >>> Arguments(int, (bool, 2)) >>> # Zero or more strings >>> Arguments((str, ...)) >>> # Same as the above >>> Arguments() >>> # One or more strings >>> Arguments(str, (str, ...)) >>> # Between 2 and 4 strings >>> Arguments((str, range(2, 4))) >>> # One int and zero or more strings >>> Arguments(int, (str, ...)) >>> # ERROR: there can be no patterns after a (type, ...)-style pattern >>> Arguments(int, (int, ...), str) >>> # ERROR: there can be no patterns after a (type, range)-style pattern >>> Arguments(int, (int, range(1, 2)), str)
- convert(args: Sequence[str], allow_extra_args=False) Union[List[Any], Any]
Convert (and consequently validate) a list of
args
that were specified on the command line to a list of arguments conforming topattern
.If the conversion of any of the arguments throws an exception, the conversion (and validation) will fail. (TODO exception)
If the pattern only expects one argument, then the single parsed argument will be returned, instead of a list with one element.
- Parameters
args – Arguments to convert.
allow_extra_args – Do not raise an exception if there are more
args
than can fit intoself.pattern
.
- assign(args: List[str], allow_extra_args=False)
Assign a set of arguments specified on the command line to be held by this instance.
The arguments are converted and validated using
convert()
in order to conform topattern
.- Parameters
allow_extra_args – See
convert()
.
Returns
List of the converted arguments.
Advanced
Meta
- class Meta(*args, **kwargs)
Meta wrapper for
Command
that can be used to access special attributes ofCommand
.Notes
Do not modify the
options
,command
,subcommands
andargument_aliases
attributes.
- call(*args, **kwargs)
Callback function for when this command is invoked.
You can freely assign this method on an instance of
Meta
. Just keep in mind that the signature must support all arguments and options that can be passed to it after the command is parsed. The function can but need not have aself
argument; but if it does, it must be the first. This argument can be used to access the associatedResolvedCommand
object.
ResolvedCommand
- class ResolvedCommand(command: Command, deepcopy=True)
Wrapper around
Command
that provides access to option values and argument values as if they were regular attributes.Examples
>>> # Initialize a simple command with an option >>> cmd = Command("test") >>> cmd.option = Option("--option", default="DEFAULT") >>> # Convert Command into a ResolvedCommand >>> parsed_cmd = ResolvedCommand(cmd) >>> print(parsed_cmd.option) DEFAULT >>> # Assignment works like with a regular command >>> parsed_cmd.option = "NON_DEFAULT" >>> print(parsed_cmd) NON_DEFAULT
dryparse.decorators
The decorator API.
- command(func: Callable[[...], Any])
Take a callable and turn it into a
Command
object.Each positional argument will be converted to an
Arguments
object whosepattern
will be generated using the type annotation.Each keyword argument will be converted to an
Option
object, again using the information from the type annotation. If the annotation is an instance ofOption
, this instance will be used directly.IMPORTANT: Type annotations must be actual types and not
Union
,Any
etc. This is required because the type will used to convert CLI arguments into their Python representations. As a special case, the annotation for keyword arguments can be an instance ofOption
.Notes
func
will become thecall
attribute associated with theCommand
object returned by this decorator. You should read the documentation ofcall
.If a parameter is neither annotated nor has a default value, its type is assumed to be
str
.The text for specifying an option on the command line is derived from the argument name in the following way:
The short text is formed by prepending a
-
before the first character of the argument name. Note that if multiple argument names start with the same character, only the first one will get a short text.Example:
recursive
becomes-r
.The long text is formed by prepending
--
before the argument name, additionally replacing all_
characters with-
.Example:
work_dir
becomes--work-dir
.
- Raises
AnnotationMustBeTypeOrSpecialError – If a type annotation is not a
type
, or an instance ofOption
, the latter only being allowed for keyword arguments.
See also
- subcommand(func: Callable[[Command], Any])
Decorator used to register a subcommand inside a class’s context.
dryparse.types
Module containing special option types.
- class OptionType(value: str)
Base class for all option value types.
You can create custom types by subclassing this class.
Attributes
- takes_argument: bool
Whether the option value is specified as an argument to the option or is derived from the presence of the option itself.
- value: Any
The option argument converted to an arbitrary type.
- class Counter(value: str)
A counter that increments each time an option is specified.
- class Bool(value: str)
A bool type that understands
"true"
,"True"
,"false"
,"False"
and empty string.
dryparse.parser
Functions for parsing the command line, i.e. converting command line arguments into their object representations.
- parse(command: Command, args: Optional[List[str]] = None)
Parse
args
intocommand
.If unspecified,
args
will fall back tosys.argv
.
- parse_arg(command: Command, arg: str) Union[Tuple[Optional[Option], Optional[Any]], Command]
Parse
arg
using the scheme fromcommand
.The argument can be an option (or option + value), a positional argument or a subcommand. For options, all the usual ways of specifying a CLI option are supported:
Long version:
--long
/--long=<value>
for bool/non-bool optionsShort version:
-s
/-s<value>
for bool/non-bool options
Returns
- (option, value)
Option object and its value, or
None
if the value was not specified inarg
(which means that the value must be found in the next argument on the command line, unless it’s a bool option).
Notes
This function does not take into consideration the case when the option name is specified as one argument and the option value as another argument.
dryparse.context
Context information.
- class Context(args=None, command_arg_index=None)
Context information, mainly from the parser.
- property args: Optional[List[str]]
Command line arguments of the current context.
- context = <dryparse.context.Context object>
Use this to query context information.
dryparse.util
Utility functions and objects used throughout dryparse.
- parse_str(text: str, target_type: type)
Parse string into primitive type
type
.
- first_token_from_regex(regex: str) Pattern
Get first valid token from regular expression
regex
.The first valid token is the smallest substring taken from the beginning of
regex
that forms a valid regex by itself.
- class reassignable_property(getter: Callable[[Any], Any])
Property whose getter function can be assigned per instance.
Caveats
The getter must behave as if its single parameter is the only information it knows about the instance that owns this property. This is necessary to properly facilitate deep copying. (TODO: enhance and clarify example)
Example:
>>> class C: >>> @reassignable_property >>> def prop(self): >>> return "default_value" >>> >>> def __init__(self, value): >>> self.value = value >>> # Correct: >>> self.prop = lambda self_: self_.value >>> # Wrong: >>> self.prop = lambda _: self.value
- verify_function_callable_with_args(func, *args, **kwargs)
Verify if
func(*args, **kwargs)
is a valid call without actually calling the function. If yes, do nothing, else raise an exception.- Raises
dryparse.errors.CallbackDoesNotSupportAllArgumentsError – If the verification fails.
dryparse.errors
All errors that can be raised directly by dryparse.
- exception DryParseError
Base class for all dryparse exceptions.
- exception OptionRequiresArgumentError(option: Optional[str] = None)
- exception OptionDoesNotTakeArgumentsError(option: Optional[str] = None)
- exception OptionArgumentTypeConversionError(argument: Optional[str] = None, argtype: Optional[type] = None)
- exception InvalidArgumentPatternError
- exception PatternAfterFlexiblePatternError
- exception ArgumentConversionError(reason: Optional[str] = None, arguments: Optional[Sequence[str]] = None, index: Optional[int] = None)
- exception VariadicKwargsNotAllowedError
- exception ValueConversionError
- exception CallbackDoesNotSupportAllArgumentsError
- exception NotEnoughPositionalArgumentsError
- exception TooManyPositionalArgumentsError
- exception ReadOnlyAttributeError(name: str)
- exception AnnotationMustBeTypeOrSpecialError(param: Parameter)
- exception SelfNotFirstArgumentError
If you have read the introductory sections, then you know that there is a decorator API and an object API. The object API is the basis for everything, and the decorator API is just syntactic sugar around it.
Contributing
Documentation
Shell code must be added like this:
.. prompt:: bash
shell command here
When adding python code examples, dryparse objects must link to their corresponding documentation:
- Source:
.. autolink-preface:: from dryparse.objects import Command .. code:: python cmd = Command("test")
- Result:
cmd = Command("test")
- Source:
.. code:: python cmd = Command("test")
- Result:
cmd = Command("test")
For more info, see the documentation of sphinx-codeautolink.
In a nutshell, dryparse is a CLI parser that makes it easy to turn regular functions and objects into command line commands and options. It works out of the box, with default behaviors that follow established practices. In addition, it provides excellent customizability and an object model that gives you the power to do anything you want with it.
As an appetizer, let’s try to recreate the ubiquitous cp
command:
@dryparse.command
def cp(
*files, link=False, force=False, target_directory: str = None
):
"""
Copy files and directories
"""
... # Logic goes here
Do this in your program’s entrypoint:
dryparse.parse(cp, sys.argv)
When someone runs this in the shell:
cp --link -r --target-directory "/tmp/" source/ b.txt
this will run in the python world:
cp("source/", "b.txt", link=True, recursive=True, target_directory="/tmp/")
This works out of the box too (help is automatically generated from the function docstring):
cp --help
A more holistic example:
docker run -ite ENVVAR1=1 --env ENVVAR2=2 \
--volume=/:/mnt:ro -v /home:/h \
alpine sh
Hint
-i
is short for --interactive
-t
is short for --tty
-e
is short for --env
-v
is short for --volume
docker.run("alpine", "sh",
interactive=True,
tty=True,
env=[("ENVVAR1", "1"), ("ENVVAR2", "2")],
volume=[("/", "/mnt", "ro"), ("/home", "/h")])
Problems
When a command contains a subcommand and option with the same name
Todo
Structure
Walkthrough
Topic-based guide
Advanced use cases
Api