Config Files
For more complicated CLI applications, it is common to have an external user configuration file. For example, the popular python tools poetry, ruff, and pytest are all configurable from a pyproject.toml file. The App.config attribute accepts a callable (or list of callables) that add (or remove) values to the parsed CLI tokens. The provided callable must have signature:
def config(app: "App", commands: Tuple[str, ...], arguments: ArgumentCollection):
"""Modifies the argument collection inplace with some injected values.
Parameters
----------
app: App
The current command app being executed.
commands: Tuple[str, ...]
The CLI strings that led to the current command function.
arguments: ArgumentCollection
Complete ArgumentCollection for the app.
Modify this collection inplace to influence values provided to the function.
"""
...
The provided config does not have to be a function; all the Cyclopts builtin configs are classes that implement the __call__ method. The Cyclopts builtins offer good standard functionality for common configuration files like yaml or toml.
TOML Example
In this example, we create a small CLI tool that counts the number of times a given character occurs in a file.
# character-counter.py
import cyclopts
from cyclopts import App
from pathlib import Path
app = App(
name="character-counter",
config=cyclopts.config.Toml(
"pyproject.toml", # Name of the TOML File
root_keys=["tool", "character-counter"], # The project's namespace in the TOML.
# If "pyproject.toml" is not found in the current directory,
# then iteratively search parenting directories until found.
search_parents=True,
),
)
@app.command
def count(filename: Path, *, character="-"):
print(filename.read_text().count(character))
if __name__ == "__main__":
app()
Running this code without a pyproject.toml present:
$ python character-counter.py count README.md
70
$ python character-counter.py count README.md --character=t
380
We can have the new default character be t by adding the following to pyproject.toml:
[tool.character-counter.count]
character = "t"
Rerunning the app without a specified --character will result in using the toml-provided value:
$ python character-counter.py count README.md
380
User-Specified Config File
Extending the above TOML Example, what if we want to allow the user to specify the toml configuration file? This can be accomplished via a Meta App.
# character-counter.py
from pathlib import Path
from typing import Annotated
import cyclopts
from cyclopts import App, Parameter
app = App(name="character-counter")
@app.command
def count(filename: Path, *, character="-"):
print(filename.read_text().count(character))
@app.meta.default
def meta(
*tokens: Annotated[str, Parameter(show=False, allow_leading_hyphen=True)],
config: Path = Path("pyproject.toml"),
):
app.config = cyclopts.config.Toml(
config,
root_keys=["tool", "character-counter"],
search_parents=True,
)
app(tokens)
if __name__ == "__main__":
app.meta()
Environment Variable Example
To automatically derive and read appropriate environment variables, use the cyclopts.config.Env class. Continuing the above TOML example:
# character-counter.py
import cyclopts
from pathlib import Path
app = cyclopts.App(
name="character-counter",
config=cyclopts.config.Env(
"CHAR_COUNTER_", # Every environment variable will begin with this.
),
)
@app.command
def count(filename: Path, *, character="-"):
print(filename.read_text().count(character))
app()
Env assembles the environment variable name by joining the following components (in-order):
The provided
prefix. In this case, it is"CHAR_COUNTER_".The command and subcommand(s) that lead up to the function being executed.
The parameter's CLI name, with the leading
--stripped, and hyphens-replaced with underscores_.
Running this code without a specified --character results in counting the default - character.
$ python character-counter.py count README.md
70
By exporting a value to CHAR_COUNTER_COUNT_CHARACTER, that value will now be used as the default:
$ export CHAR_COUNTER_COUNT_CHARACTER=t
$ python character-counter.py count README.md
380
$ python character-counter.py count README.md --character=q
3
In-Memory Dict
For configurations that come from sources other than files, use cyclopts.config.Dict.
# character-counter.py
import json
import cyclopts
from pathlib import Path
def fetch_config():
"""Simulate fetching configuration from an API."""
return {"count": {"character": "e"}}
config_data = fetch_config_from_api()
app = cyclopts.App(
name="character-counter",
config=cyclopts.config.Dict(
fetch_config,
# Optional: provide custom source identifier for better error messages
source="api",
),
)
@app.command
def count(filename: Path, *, character="-"):
print(filename.read_text().count(character))
if __name__ == "__main__":
app()
Combining Multiple Config Sources
You can combine multiple config sources in a single application by passing a sequence to App.config. Each configuration is applied sequentially.
In the following example, we combine a TOML file and environment variables, allowing environment variables to override TOML settings:
# character-counter.py
import cyclopts
from pathlib import Path
app = cyclopts.App(
name="character-counter",
config=[
# Since Env comes before Toml, it has priority.
cyclopts.config.Env("CHAR_COUNTER_"),
cyclopts.config.Toml(
"pyproject.toml",
root_keys=["tool", "character-counter"],
search_parents=True,
),
],
)
@app.command
def count(filename: Path, *, character="-"):
print(filename.read_text().count(character))
if __name__ == "__main__":
app()
With this setup, the configuration is resolved in the following order:
CLI arguments (if provided) override everything else
Environment variables (prefixed with
CHAR_COUNTER_) can override TOML valuesTOML file (pyproject.toml) provides the base configuration
Python default the default value
-in the python code.
For example, with pyproject.toml containing:
[tool.character-counter.count]
character = "t"
You can override it via environment variable:
$ CHAR_COUNTER_COUNT_CHARACTER=a python character-counter.py count README.md
Or via CLI argument:
$ python character-counter.py count README.md --character=x