Skip to main content

CLI + config application

Project description

Tuler

A tool for building python CLIs

What does it do?

Tuler is heavily inspired by Typer, another CLI tool, with a few key differences.

Here is the basic pattern for Tuler.

from tuler import App

app = App()

@app.command()
def greet_friend():
    print("hey friend!")

if __name__ == "__main__":
    app.run()
$ python3 foo.py greet_friend
hey friend!

@app.command() registers greet_friend as a command in app. app.run() will parse sys.argv and either run greet_friend() or display a help message.

You can register multiple commands that use arguments and options.

from tuler import App

app = App()

@app.command()
def greet_friend(name):
    print(f"hey {name}!")

@app.command()
def greet_with_message(name, message="my friend!"):
    print(f"hello {name}, {message}")

if __name__ == "__main__":
    app.run()
$ python3 foo.py greet_friend Leo
hey Leo!
$ python3 foo.py greet_with_message Mark --message "a handshake is available on request."
hello Mark, a handshake is available on request.

You also get a built in help screen.

$ python3 foo.py --help

USAGE: foo.py <COMMAND> [OPTIONS]

COMMANDS:
   greet_friend
   greet_with_message
$ python3 foo.py greet_friend --help

USAGE: foo.py greet_with_message <ARGUMENTS> [OPTIONS]

ARGUMENTS:
   <NAME>


GREET_WITH_MESSAGE OPTIONS:
      --message      [default: 'you fool!']

Tuler handles global options by using templates.

from tuler import App
from dataclasses import dataclass

@dataclass
class GlobalOptionsTemplate:
    verbose: bool = False
    directory: str = "/"

app = App(GlobalOptionsTemplate)

@app.command()
def print_global_opts():
    print("verbose:", app.opts.verbose)
    print("directory:", app.opts.directory)

if __name__ == "__main__":
    app.run()
$ python3 foo.py print_global_opts
verbose: False
directory: /

$ python3 foo.py print_global_opts --verbose --directory "/dev/"
verbose: True
directory: /dev/

A template is a dataclass that lays out options via fields. All fields must have default values. When a template is passed into App(), app.opts is the initialized template dataclass with the values of your options.

Arguments and options can be annotated to include additional metadata.

from dataclasses import dataclass
from typing import Annotated
from tuler import App, Argument, Flag, Option

@dataclass
class GlobalOptionsTemplate:
    verbose: Annotated[
        bool,
        Flag(
            short_name="v",
            hint="How much output should the program produce",
        ),
    ] = False
    directory: Annotated[
        str,
        Option(
            short_name="d",
            hint="Which directory to print.",
        ),
    ] = "/"

app = App(GlobalOptionsTemplate)

@app.command()
def magic(
    x: Annotated[
        int,
        Argument(
            hint="Magical argument",
            parser=int,
            validator=lambda v: v > 100,
        ),
    ],
):
    print(x)

if __name__ == "__main__":
    app.run()
$ python3 foo.py --help
USAGE: foo.py <COMMAND> [OPTIONS]

COMMANDS:
   magic
 

OPTIONS:
   -v   --[no-]verbose    How much output should the program produce   [default: no-verbose]
   -d   --directory       Which directory to print.                           [default: '/']

$ python3 foo.py magic --help
USAGE: foo.py magic <ARGUMENTS> [OPTIONS]

ARGUMENTS:
   <X>    Magical number that must be greater than 100


OPTIONS:
   -v   --[no-]verbose    How much output should the program produce   [default: no-verbose]
   -d   --directory       Which directory to print.                           [default: '/']

$ python3 foo.py magic 50
Argument <X> did not pass validation!

You can also pass in a configuration toml file to App().

from tuler import App
from dataclasses import dataclass

@dataclass
class Options:
    verbose: bool = False
    foo: str = "bar"

app = App(config_file="config.toml")

@app.command()
def greet_friend(name):
    print(f"hey {name}!")

@app.command()
def greet_with_message(name, message="my friend!"):
    print(f"hello {name}, {message}")

if __name__ == "__main__":
    app.run()

config.toml:

verbose = true
foo = "bazz"

[greet_with_message]
message = "from config!"

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

tuler-0.1.0.tar.gz (9.5 kB view details)

Uploaded Source

Built Distribution

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

tuler-0.1.0-py3-none-any.whl (6.8 kB view details)

Uploaded Python 3

File details

Details for the file tuler-0.1.0.tar.gz.

File metadata

  • Download URL: tuler-0.1.0.tar.gz
  • Upload date:
  • Size: 9.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.5.9

File hashes

Hashes for tuler-0.1.0.tar.gz
Algorithm Hash digest
SHA256 2a7edcf94eb7bf7a5f30818f8e5873767644c45fd81cf7088ebe7c3a58b0d190
MD5 38de2176f043ad30d1c9ce0cd0755284
BLAKE2b-256 b1c7feecd969f89aff95aa4afc707e8718727d8c606fe5049e1c946439cd339d

See more details on using hashes here.

File details

Details for the file tuler-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: tuler-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 6.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.5.9

File hashes

Hashes for tuler-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 7776c2cbde4fc168ebcff8b45afbb26fa24c1c74f5a7ca7a1548a822faa9fcda
MD5 568a8eea07bea37d591bd15276b629e7
BLAKE2b-256 bbc83688d11a3acd3079e47d3e88dd583e772e5615bea47888c6426cf1aedec0

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