Clout: Command-Line Object Utility Tool¶
docs | |
---|---|
code | |
tests | |
package |
Convert dataclasses into a command-line interface.
Quickstart¶
To install, use
pip install clout
Define some dataclasses and convert them into a command-line interface.
import attr
import click
import clout
@attr.dataclass
class DB:
host: str
port: int
@attr.dataclass
class Config:
db: DB
dry_run: bool
cli = clout.Command(Config)
print(cli.build())
$ myapp --dry-run db --host example.com --port 9999
Config(db=DB(host='example.com', port=9999), dry_run=True)
See the full docs for more information: https://clout.readthedocs.io/
Contents¶
Clout: Command-Line Object Utility Tool¶
docs | |
---|---|
code | |
tests | |
package |
Convert dataclasses into a command-line interface.
Quickstart¶
To install, use
pip install clout
Define some dataclasses and convert them into a command-line interface.
import attr
import click
import clout
@attr.dataclass
class DB:
host: str
port: int
@attr.dataclass
class Config:
db: DB
dry_run: bool
cli = clout.Command(Config)
print(cli.build())
$ myapp --dry-run db --host example.com --port 9999
Config(db=DB(host='example.com', port=9999), dry_run=True)
See the full docs for more information: https://clout.readthedocs.io/
Usage¶
Convert dataclasses into a command-line interface.
Quickstart¶
To install, use
pip install clout
Define some dataclasses and convert them into a command-line interface.
import attr
import click
import clout
@attr.dataclass
class DB:
host: str
port: int
@attr.dataclass
class Config:
db: DB
dry_run: bool
cli = clout.Command(Config)
print(cli.build())
$ myapp --dry-run db --host example.com --port 9999
Config(db=DB(host='example.com', port=9999), dry_run=True)
A decorator¶
To run a function with the built object, you can use a decorator, clout.command()
.
Using clout.Command.main()
, the program will exit immediately afterwards.
import attr
import clout
@attr.dataclass
class Person:
name: str = attr.ib(metadata={"clout": {"cli": dict(envvar="EXAMPLE_NAME")}})
age: int
@clout.command(Person)
def greet(person):
print(f"Hello, {person.name}!")
if __name__ == "__main__":
print(greet.main())
$ EXAMPLE_NAME=Alice python examples/decorator.py --age 21
Hello, Alice!
A callback¶
If you don’t want to use a decorator, you can use clout.Command()
directly, and
pass the callback=
argument.
import attr
import click
import clout
@attr.dataclass
class Person:
name: str
age: int
def greet(person):
print(f"Hello, {person.name}!")
cli = clout.Command(Person, callback=greet)
if __name__ == "__main__":
cli.main()
Extended example¶
Now for a longer example.
- Define a nested configuration object.
- Load the object from the CLI, getting missing values from environment variables,
configuration file, and dataclass
default=
values.
Here we define the config file in the Freedesktop standard directory.
# ~/.config/myapp/config.toml
[config]
dry_run=true
Set an environment variable and run the app.
% MYAPP_PRIORITY=2 python examples/long.py --debug user --name Alice db --host example.com --port 9999 user --name Bob
Config(db=DB(host='example.com', port=9999, user=User(name='Bob')), debug=True, dry_run=True, user=User(name='Alice'), priority=2.0, logging=True)
The code:
import pathlib
import appdirs
import attr
import click
import toml
import clout
@attr.dataclass
class User:
name: str
@attr.dataclass
class DB:
host: str
port: int
user: User
@attr.dataclass
class Config:
db: DB
debug: bool
dry_run: bool
user: User
priority: float = attr.ib(
default=0,
metadata={
"clout": {
"cli": dict(param_decls=["--priority"], help="App priority value")
}
},
)
logging: bool = True
APP_NAME = "myapp"
# Read config file.
with open("examples/config.toml") as f:
CONFIG_FILE_DATA = toml.load(f)
# Read from environment_variables prefixed `MYAPP_`,
# such as MYAPP_PRIORITY=10.
ENVVAR_DATA = clout.load_env(Config, prefix=APP_NAME)
# Combine config file and envvars to set CLI defaults.
CONTEXT_SETTINGS = dict(default_map=clout.DeepChainMap(ENVVAR_DATA, CONFIG_FILE_DATA))
# Define the CLI.
commands = [
clout.Command(
name="config",
type=Config,
context_settings=CONTEXT_SETTINGS,
help="Run the app with given configuration object.",
)
]
cli = click.Group(commands={c.name: c for c in commands})
if __name__ == "__main__":
# Run the CLI.
print(cli.main(standalone_mode=False))
Reference¶
clout package¶
Submodules¶
clout.exceptions module¶
-
exception
clout.exceptions.
CloutException
[source]¶ Bases:
Exception
Base exception for Clout exceptions.
-
exception
clout.exceptions.
MissingInput
(group, string, found)[source]¶ Bases:
clout.exceptions.CloutException
Raised for missing command-line arguments.
-
exception
clout.exceptions.
ValidationError
[source]¶ Bases:
clout.exceptions.CloutException
Raised for invalid command-line arguments.
Module contents¶
-
class
clout.
Command
(type, *args, name=None, app_name=None, callback=<function Command.<lambda>>, params=None, context_settings=None, epilog=None, **kwargs)[source]¶ Bases:
click.core.Command
A
click.Command
built from anattr.dataclass()
ordataclasses.dataclass()
.
-
clout.
load_env
(type: Type[CT_co], prefix: str = '')[source]¶ Load environment variables for a class into a dict.
For example, define a class
import attr @attr.dataclass class Database: host: str port: int
Load the environment variables into a dict, setting the prefix for our app.
d = clout.load_env(Database, prefix='MYAPP') assert d == {'host': 'example.com', 'port': 1234}
Run the app with environment variables set.
export MYAPP_HOST=example.com export MYAPP_PORT=1234
-
class
clout.
DeepChainMap
(*maps)[source]¶ Bases:
collections.ChainMap
Combine multiple dicts into a deep mapping.
Lookups that fail in the first dict will be checked in the next one.
>>> import clout >>> maps = [{"a": {}}, {"a": {"b": {}}}, {"a": {"b": {"c": 1337}}}] >>> dcm = clout.DeepChainMap(*maps) >>> dcm["a"]["b"]["c"] 1337
Contributing¶
Contributions are welcome, and they are greatly appreciated! Every little bit helps, and credit will always be given.
Bug reports¶
When reporting a bug please include:
- Your operating system name and version.
- Any details about your local setup that might be helpful in troubleshooting.
- Detailed steps to reproduce the bug.
Documentation improvements¶
clout could always use more documentation, whether as part of the official clout docs, in docstrings, or even on the web in blog posts, articles, and such.
Feature requests and feedback¶
The best way to send feedback is to file an issue at https://github.com/python-clout/clout/issues.
If you are proposing a feature:
- Explain in detail how it would work.
- Keep the scope as narrow as possible, to make it easier to implement.
- Remember that this is a volunteer-driven project, and that code contributions are welcome :)
Development¶
To set up python-clout for local development:
Fork python-clout (look for the “Fork” button).
Clone your fork locally:
git clone git@github.com:your_name_here/python-clout.git
Create a branch for local development:
git checkout -b name-of-your-bugfix-or-feature
Now you can make your changes locally.
When you’re done making changes, run all the checks, doc builder and spell checker with tox one command:
tox
Commit your changes and push your branch to GitHub:
git add . git commit -m "Your detailed description of your changes." git push origin name-of-your-bugfix-or-feature
Submit a pull request through the GitHub website.
Pull Request Guidelines¶
If you need some code review or feedback while you’re developing the code just make the pull request.
For merging, you should:
- Include passing tests (run
tox
) [1]. - Update documentation when there’s new API, functionality etc.
- Add a file in
changelog.d/
describing the changes. The filename should be{id}.{type}.rst
, where{id}
is the number of the GitHub issue or pull request and{type}
is one ofbreaking
(for breaking changes),deprecation
(for deprecations), orchange
(for non-breaking changes). For example, to add a new feature requested in GitHub issue #1234, add a file calledchangelog.d/1234.change.rst
describing the change.
[1] | If you don’t have all the necessary python versions available locally you can rely on Travis - it will run the tests for each change you add in the pull request. It will be slower though … |
Tips¶
To run a subset of tests:
tox -e envname -- pytest -k test_myfeature
To run all the test environments in parallel (you need to pip install detox
):
detox
Authors¶
A full list of constributors can be found in GitHub’s overview.