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
UI
- GUI – tkinter_form
- TUI – textual
- CLI...
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)
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?