Skip to main content

Create a powerful and efficient CLI application using simple and user-friendly yaml config

Project description

Argyaml

Argyaml is a small module for creating powerful and scalable CLI applications based on a simple and user-friendly yaml configuration file.

Motivation

Argyaml is built over the argparse module, which is a part of python standard library starting python 3.2. While it works well for tiny projects that need to quickly access a few arguments and provide automatically generated help and usage messages for user, it gets very complicated and painful when it comes to large projects or your application grows in complexity.

Features

  • Independent specification of CLI commands and arguments.
  • No boilerplate code.
  • Ability to set default options for commands, groups, and arguments.
  • Automatic and optimized invocation of command handlers.
  • Handler template files generator.

Install

# pip
pip install argyaml

# poetry
poetry add argyaml

Getting started

# cli-config.yaml
prog: todo
description: My beautiful todo app
next:
  - command: new
    next:
      - command: task
        description: Create a new task
        next:
          - argument: [name]
            help: the name of task
  - command: list
    next:
      - argument: [-t, --task]
        help: display tasks only
        action: 'store_true'
from argyaml import BaseHandler

base = BaseHandler()
base.args # <-- parsed and ready-to-use arguments

Learn more about config file.

Using Handlers

Generate template files using argyaml generate:

# pip
argyaml generate

# poetry
poetry run argyaml generate

This will generate the following files:

handlers/
  _new_task.py
  _list.py

Now, whenever new task command is called, the corresponding handler init function will be invoked with all additional aruments stored in the self.args object.

# _new_task.py
from argyaml import BaseHandler

class Handler(BaseHandler.meta()):
    def __init__(self):
        print(f"Successfully created task '{self.args['name']}'!")

Modify the main file to run the base handler:

from argyaml import BaseHandler

base = BaseHandler()
base.run()

Learn more about BaseHandler and argyaml generator.

Basic concepts

Imagine that you have several commands, each containing its own sub-commands that have their own set of arguments:

add city <name>
add building --city CITY_NAME

remove city <name> [--force]
remove building --id ID

list cities
list buildings --city CITY_NAME

A minimal implementation using argparser would be the following:

import argparse

parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers()

parser_add = subparsers.add_parser('add')
parser_remove = subparsers.add_parser('remove')
parser_list = subparsers.add_parser('list')

subparsers_add = parser_add.add_subparsers()
subparsers_remove = parser_remove.add_subparsers()
subparsers_list = parser_list.add_subparsers()

# 'add' sub-commands
parser_add_city = subparsers_add.add_parser('city')
parser_add_city.add_argument('name', type=str)

parser_add_building = subparsers_add.add_parser('building')
parser_add_building.add_argument('--city', dest='city_name',
                                 required=True, type=str)

# 'remove' sub-commands
parser_remove_city = subparsers_remove.add_parser('city')
parser_remove_city.add_argument('name', type=str)
parser_remove_city.add_argument('--force', action='store_true')

parser_remove_building = subparsers_remove.add_parser('building')
parser_remove_building.add_argument('--id', required=True, type=int)

# 'list' sub-commands
parser_list_cities = subparsers_list.add_parser('cities')

parser_list_buildings = subparsers_list.add_parser('buildings')
parser_list_buildings.add_argument('--city', dest='city_name', 
                                   required=True, type=str)

# parse the arguments and transform to dict
parser.parse_args()
vars(parser.parse_args())

Lots of boilerplate code that is not easy to read. Here is an equivanet using argyaml:

# cli-config.yaml
next:
  - command: add
    next:
      - command: city
        next:
          - argument: ['name']
            type: str
      - command: building
        next:
          - argument: ['--city']
            dest: 'city_name'
            required: true
            type: str

  - command: remove
    next:
      - command: city
        next:
          - argument: ['name']
            type: str
          - argument: ['--force']
            action: store_true
      - command: building
        next:
          - argument: ['--id']
            required: true
            type: int

  - command: list
    next:
      - command: cities
      - command: buildings
        next:
          - argument: ['--city']
            dest: 'city_name'
            required: true
            type: str
from argyaml import BaseHandler

base = BaseHandler()
base.args

Configuration file

The structure of yaml configuration file is defined as follows:

<parser-options>
group:
  <subcommands-options>
next:
  - command: name
    <parser-options>
    group: ...
    next: ...
  - argument: [positional]
    <argument-options>
  - argument: [short-flag, long-flag, ...]
    <argument-options>

default: 
  group:
    <subcommands-options>
  command:
    <parser-options>
  argument:
    <argument-options>

handlers:

Next keyword

The next field is used to define a hierarchy of commands. It's a list of objects, where each object can start with either a command key, which defines a command (subcommand), or an argument key, which defines an argument (a positional argument or an option, flag) of the command. The next field can be used recursively to define subcommands and arguments within commands (subcommands).

Parser options

In the context of the above configuration file structure, <parser-options> represent the options that are specified for the top-level ArgumentParser class:

  • prog — The name of the program or a subcommand (default for program: os.path.basename(sys.argv[0]); default for subcommand: name of parent command).
  • usage — The string describing the program or a subcommand usage (by default: generated from arguments added to parser).
  • description — Text to display before the argument help (by default, no text).
  • epilog — Text to display after the argument help (by default, no text)
  • prefix_chars — The set of characters that prefix optional arguments (default: ‘-‘).
  • fromfile_prefix_chars — The set of characters that prefix files from which additional arguments should be read (default: None).
  • argument_default — The global default value for arguments (default: None).
  • add_help — Determines whether or not to add -h/--help option (default: True).
  • allow_abbrev — Allows long options to be abbreviated if the abbreviation is unambiguous (default: True).
  • exit_on_error — Determines whether or not ArgumentParser exits with error info when an error occurs (default: True).

Subcommands options

The <subcommands-options> are the same options for the add_subparsers method:

  • title — Title for the sub-parser group in help output; by default “subcommands” if description is provided, otherwise uses title for positional arguments.
  • description — Description for the sub-parser group in help output, by default None.
  • prog — Usage information that will be displayed with sub-command help, by default the name of the program and any positional arguments before the subparser argument.
  • action — The basic type of action to be taken when this argument is encountered at the command line.
  • dest — Name of the attribute under which sub-command name will be stored; by default is parent command.
  • required — Whether or not a subcommand must be provided, by default False (added in 3.7).
  • help — Help for sub-parser group in help output, by default None.
  • metavar — String presenting available sub-commands in help; by default it is None and presents sub-commands in form {cmd1, cmd2, ..}.

Argument options

argument-options are the same options for the add_argument method, except for name or flags being specified under the argument field:

  • action — The basic type of action to be taken when this argument is encountered at the command line.
  • nargs — The number of command-line arguments that should be consumed.
  • const — A constant value required by some action and nargs selections.
  • default — The value produced if the argument is absent from the command line and if it is absent from the namespace object.
  • type — The type to which the command-line argument should be converted.
  • choices — A sequence of the allowable values for the argument.
  • required — Whether or not the command-line option may be omitted (optionals only).
  • help — A brief description of what the argument does.
  • metavar — A name for the argument in usage messages.
  • dest — The name of the attribute to be added to the object returned by parse_args().

Custom defaults

The default section of the configuration file allows you to define default values for each type of option. Note that these defaults can be overridden by values inside specific commands, groups, or arguments.

Base Handler

The base handler should not necessarily be used once and only once. You can instantiate as many base handlers as you need. To create multiple base handlers, you must specify custom names that will used in further handler registration and access.

You may also want to use a different configuration file and argument list.

The BaseHandler class accepts the following options:

  • name — the custom name of base handler.
  • args — the list of arguments (by default, sys.argv[1:] is used).
  • config_path — path to configuration file.
  • handlers_dir — path to handlers directory.

Example

BaseHandler(name='Sea', handlers_dir='sea-handlers').run()
BaseHandler(name='Land', handlers_dir='land-handlers').run()
# sea-handlers/_default.py
class Handler(BaseHandler.meta('Sea')):
  print('This handler is invoked by Sea')
# land-handlers/_default.py
class Handler(BaseHandler.meta('Land')):
  print('This handler is invoked by Land')

Generator

Handler template files generator can be called with the following arguments:

  • --name NAME — the custom name of base handler.
  • --config CONFIG — path to configuration file.
  • --handlers_dir DIR — path to handlers directory.
  • --force — overwrite existing files.

Example

# sea-cli.yaml
next:
  - command: swim
# pip
argyaml generate --name Sea --dir sea-handlers --config sea-cli.yaml

# poetry
poetry run argyaml generate --name Sea --dir sea-handlers --config sea-cli.yaml

The command above will generate the following file:

from argyaml import BaseHandler

class Handler(BaseHandler.meta('Sea')):
  pass
sea-handlers/
  _swim.py

Advanced configuration

Argyaml configuration file may include an additional field handlers:

  • root — the name of handler file to be invoked if no commands specified (default: "default")
  • internal — whether to mark files as internal by adding an underscore before the name (default: true)
handlers
  root: init
  internal: false

Contributing

Feel free to open issues. Pull requests are welcome!

License

This project is licensed under the MIT License.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

argyaml-0.1.6.tar.gz (7.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

argyaml-0.1.6-py3-none-any.whl (9.3 kB view details)

Uploaded Python 3

File details

Details for the file argyaml-0.1.6.tar.gz.

File metadata

  • Download URL: argyaml-0.1.6.tar.gz
  • Upload date:
  • Size: 7.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/1.4.1 CPython/3.10.4 Linux/5.4.0-1104-azure

File hashes

Hashes for argyaml-0.1.6.tar.gz
Algorithm Hash digest
SHA256 90b2759588f90d84d2d0fa0a18b334e3c33d9bab599ff5c864a5da61c5cce6da
MD5 cfbb6c897eeeb4215987f7b4ede27876
BLAKE2b-256 1e1ee22a97f8587c2edb97ad135cc2267f52a5497ef77e05388750d345de7ee8

See more details on using hashes here.

File details

Details for the file argyaml-0.1.6-py3-none-any.whl.

File metadata

  • Download URL: argyaml-0.1.6-py3-none-any.whl
  • Upload date:
  • Size: 9.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/1.4.1 CPython/3.10.4 Linux/5.4.0-1104-azure

File hashes

Hashes for argyaml-0.1.6-py3-none-any.whl
Algorithm Hash digest
SHA256 ec8e967c1cd0d23ca588c661fdca802f5a518abb28ee219c3dcd9c8eec77e4f4
MD5 8af0004de0ce88734f35313650a7b896
BLAKE2b-256 3a46e809d3aedc0f9c93e8c1c49951a5ca2cc195277dedef4e3884e40f249c49

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page