Mininterface

  • Simple program
  • Connected libraries
  • Library overview

Simple program

@dataclass class Env: """ This calculates something. """ my_flag: bool = False """ This switches the functionality """ my_number: int = 4 """ This number is very important """ ... # a calculation

Making a UI

import tkinter as tk from dataclasses import dataclass from tkinter import messagebox # Create the GUI application class EnvEditor(tk.Tk): def __init__(self, env: Env): super().__init__() self.env = env self.title("Edit Env Attributes") # Create label and input field for `my_flag` (bool) self.my_flag_var = tk.BooleanVar(value=self.env.my_flag) self.my_flag_label = tk.Label(self, text="my_flag flag (True/False):") self.my_flag_label.grid(row=0, column=0, padx=10, pady=5) self.my_flag_entry = tk.Checkbutton(self, variable=self.my_flag_var) self.my_flag_entry.grid(row=0, column=1, padx=10, pady=5) # Create label and input field for `my_number` (int) self.number_var = tk.IntVar(value=self.env.my_number) self.number_label = tk.Label(self, text="Important Number:") self.number_label.grid(row=1, column=0, padx=10, pady=5) self.number_entry = tk.Entry(self, textvariable=self.number_var) self.number_entry.grid(row=1, column=1, padx=10, pady=5) # Update button self.update_button = tk.Button(self, text="Update", command=self.update_env) self.update_button.grid(row=2, column=0, columnspan=2, pady=10) def update_env(self): # Update the Env object with new values self.env.my_flag = self.my_flag_var.get() self.env.my_number = self.number_var.get() # Show confirmation messagebox.showinfo("Updated", f"Env updated!\nmy_flag: {self.env.my_flag}\nImportant Number: {self.env.my_number}") # Example usage env = Env() app = EnvEditor(env) app.mainloop()

Making a UI

from mininterface import run run(Env).form()

Fetching a CLI

import click @click.command() @click.option('--my_flag', type=bool, is_flag=True, help='This switches the functionality') @click.option('--my_number', type=int, help='This number is very important') def main(my_flag, my_number): # Create an Env object with CLI inputs env = Env(my_flag=my_flag, my_number=my_number)

Fetching a CLI

from mininterface import run run(Env).form()

$ ./program.py --help
usage: program.py [-h] [-v] [--my-flag | --no-my-flag] [--my-number INT]
This calculates something.
╭─ options ─────────────────────────────────────────────────────────────────╮
│ -h, --help              show this help message and exit                   │
│ -v, --verbose           Verbosity level. Can be used twice to increase.   │
│ --my-flag, --no-my-flag This switches the functionality (default: False)  │
│ --my-number INT         This number is very important (default: 4)        │
╰───────────────────────────────────────────────────────────────────────────╯
$ cat program.yaml
my_number: 100
    
                        
./program.py --help
usage: program.py [-h] [-v] [--my-flag | --no-my-flag] [--my-number INT]

This calculates something.

╭─ options ───────────────────────────────────────────────────────────────╮
│ -h, --help             show this help message and exit                  │
│ -v, --verbose          Verbosity level. Can be used twice to increase.  │
│ --my-flag, --no-my-flag                                                 │
│                        This switches the functionality (default: False) │
│ --my-number INT        This number is very important (default: 100)     │
╰─────────────────────────────────────────────────────────────────────────╯
     
$ ./program.py --help
    usage: program.py [-h] [-v] [--my-flag | --no-my-flag] [--my-number INT]
    This calculates something.
    ╭─ options ─────────────────────────────────────────────────────────────────╮
    │ -h, --help              show this help message and exit                   │
    │ -v, --verbose           Verbosity level. Can be used twice to increase.   │
    │ --my-flag, --no-my-flag This switches the functionality (default: False)  │
    │ --my-number INT         This number is very important (default: 4)        │
    ╰───────────────────────────────────────────────────────────────────────────╯
# Access via `m.env` attribute
m = run(Env)
print(m.env)
# Env(my_flag=False, my_number=4)
@dataclass
class Env:
    """ This calculates something. """
    my_flag: bool = False
    """ This switches the functionality """
    my_number: int = 4
    """ This number is very important """

... # a calculation

Overview

graph LR
subgraph mininterface
run --> GUI
run --> TUI
run --> env
CLI --> run
id1[config file] --> CLI
end
program --> run
                

Connected libraries

background analysis

CLI parser

  • argparse parser.add_argument('--my_number', type=int, help='This number is very important')
  • click 15⭐ @click.option('--my_number', type=int, help='This number is very important')
  • fire 27⭐ def my_function(my_number: int)
typer 15⭐
docopt/-ng 8⭐
twisted.python.usage 5⭐
Gooey 20⭐
plumbum
pydantic-cli
dataclass-click
typed-args
jsonargparse
glacier
clipstick
cyto
clippy
cliche
cement
cleo
clize
plac
arguably
anyfig
arglite
recline
cli3
clint
interfacy-cli
oneface
typed-argument-parser
SimpleParsing

Tyro

  • Standard python docstring
    my_text: str = "" """ My help text """
  • Standard python comment
    my_text: str = "" # My help text
  • Function parameter
    my_text: str = Field(default="", description="My help text", cli=('-t', '--my_text'), ...)
  • Variable annotation
    my_text: Annotated[str, "..."] = ""

Where is the docstring shown

my_flag: bool = False """ This switches the functionality """

  • At the variable declaration
  • The user sees it in the CLI (no need to re-document as with argparse)
  • The programmer sees it in the IDE

Library overview

What's available

  • config object
  • run – main entry
  • Mininterface – dialog methods
  • Tag – value wrapper
  • Facet – UI look

config object

  • standard dataclass
  • pydantic model
  • attrs class

@dataclass
class Env:
    my_number: int = 1
    """ A dummy number """

    my_boolean: bool = True
    """ A dummy boolean """

    my_conditional_number: int | None = None
    """ A number that can be null if left empty """

    my_path: Path = Path("/tmp")
    """ A dummy path """

    my_paths: list[Path] | None = None
    """ A dummy path list """

m = run(Env).form()

Nested configuration

from mininterface import run
@dataclass
class FurtherConfig:
    host: str = "example.org"

@dataclass
class Env:
    further: FurtherConfig
    host: str = "localhost"

m = run(Env)
print(m.env.further.host)  # example.org
m.form()

run – main entry

  • env_class – Dataclass with the configuration.
  • ask_on_empty_cli – If program was launched with no arguments (empty CLI), invokes self.form() to edit the fields
  • title – Window title
  • config_file – Hard code the config path
  • add_verbosity – Shortcut to logging
  • ask_for_missing – If some required fields are missing at startup, we ask for them in a UI instead of program exit
  • interface – Specify the UI. (Default – GUI or TUI.)

ask_for_missing=False

from mininterface import run
@dataclass
class Env:
    required_number: int
    """ This is an important number """
    my_number: int = 5
    """ This is a defaulted number """

m = run(Env, ask_for_missing=False)
$ ./program.py
╭─ Required options ──────────────────────────────────────╮
│ The following arguments are required: --required-number │
│ ─────────────────────────────────────────────────────── │
│ Argument helptext:                                      │
│     --required-number INT                               │
│         (required)                                      │
│ ─────────────────────────────────────────────────────── │
│ For full helptext, run program.py --help                │
╰─────────────────────────────────────────────────────────╯

ask_for_missing=True

from mininterface import run
@dataclass
class Env:
    required_number: int
    """ This is an important number """
    my_number: int = 5
    """ This is a defaulted number """

m = run(Env)

Dialog methods

Javascript

  • alert
  • confirm (yes/no)
  • prompt (text input)

Python

  • input (text input)
  • THAT'S ALL

Mininterface – dialog methods

  • alert
  • ask
  • ask_number
  • choice
  • form
  • is_yes
  • is_no

Mininterface – ask_number

from mininterface import run m = run() # receives a Mininterface object m.ask_number("What's your age?")

Mininterface – form

from mininterface import run
m = run()
m.form({"my_number": 1,
        "my_boolean": True,
        "my_path": Path("/tmp")})

Mininterface – choice

Either put options in an iterable

m = run()
m.choice([1, 2])

Or to a dict `{name: value}`. Then name are used as labels.

m.choice({"one": 1, "two": 2})

Tag – value wrapper

from mininterface import run, Tag
m = run()
m.form({"My boolean": Tag(True, "This is my boolean")})

Tag – value wrapper

Validation – user cannot leave the field empty:

from mininterface.validators import not_empty m.form({"my_text", Tag("", validation=not_empty)})

Facet – UI look

def callback(tag: Tag): tag.facet.set_title(f"Value changed to {tag.val}") m = run() m.facet.set_title("Click the checkbox") m.form({"My choice": Tag(choices=["one", "two"], on_change=callback)})

Feedback welcome

... concept, parameters' order, method names...

  • m.is_yes() / m.yes()?
  • m.choice() / m.select()?
  • tag.validation / tag.check?