Metadata-Version: 2.1
Name: mininterface
Version: 0.5.0
Summary: A minimal access to GUI, TUI, CLI and config
Home-page: https://github.com/CZ-NIC/mininterface
License: GPL-3.0-or-later
Author: Edvard Rejthar
Author-email: edvard.rejthar@nic.cz
Requires-Python: >=3.10,<4.0
Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Requires-Dist: envelope
Requires-Dist: pyyaml
Requires-Dist: requests
Requires-Dist: textual
Requires-Dist: tkinter-tooltip
Requires-Dist: tkinter_form (==0.1.5.2)
Requires-Dist: tyro
Description-Content-Type: text/markdown

# Mininterface – access to GUI, TUI, CLI and config files
[![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0)
[![Build Status](https://github.com/CZ-NIC/mininterface/actions/workflows/run-unittest.yml/badge.svg)](https://github.com/CZ-NIC/mininterface/actions)

Write the program core, do not bother with the input/output.

![Hello world example: GUI window](https://github.com/CZ-NIC/mininterface/blob/main/asset/hello-world.png?raw=True "A minimal use case – GUI")
![Hello world example: TUI fallback](https://github.com/CZ-NIC/mininterface/blob/main/asset/hello-tui.webp?raw=True "A minimal use case – TUI fallback")

Check out the code, which is surprisingly short, that displays such a window or its textual fallback.

```python
from dataclasses import dataclass
from mininterface import run

@dataclass
class Env:
    """Set of options."""
    test: bool = False  # My testing flag
    important_number: int = 4  # This number is very important

if __name__ == "__main__":
    env = run(Env, prog="My application").get_env()
    print(env.important_number)  # suggested by the IDE with the hint text "This number is very important"
```

## You got CLI
It was all the code you need. No lengthy blocks of code imposed by an external dependency. Besides the GUI/TUI, you receive powerful YAML-configurable CLI parsing.

```bash
$ ./hello.py
usage: My application [-h] [--test | --no-test] [--important-number INT]

Set of options.

╭─ options ──────────────────────────────────────────────────────────╮
│ -h, --help              show this help message and exit            │
│ --test, --no-test       My testing flag (default: False)           │
│ --important-number INT  This number is very important (default: 4) │
╰────────────────────────────────────────────────────────────────────╯
```

## You got config file management
Loading config file is a piece of cake. Alongside `program.py`, put `program.yaml` and put there some of the arguments. They are seamlessly taken as defaults.

```yaml
important_number: 555
```

## You got dialogues
Check out several useful methods to handle user dialogues. Here we bound the interface to a `with` statement that redirects stdout directly to the window.

```python
with run(Env) as m:
    print(f"Your important number is {m.env.important_number}")
    boolean = m.is_yes("Is that alright?")
```

![Small window with the text 'Your important number'](https://github.com/CZ-NIC/mininterface/blob/main/asset/hello-with-statement.webp?raw=True "With statement to redirect the output")
![The same in terminal'](https://github.com/CZ-NIC/mininterface/blob/main/asset/hello-with-statement-tui.avif?raw=True "With statement in TUI fallback")

# Contents
- [Mininterface – GUI, TUI, CLI and config](#mininterface-gui-tui-cli-and-config)
- [Background](#background)
- [Installation](#installation)
- [Docs](#docs)
  * [`mininterface`](#mininterface)
    + [`run(config=None, interface=GuiInterface, **kwargs)`](#runconfignone-interfaceguiinterface-kwargs)
  * [Interfaces](#interfaces)
    + [`Mininterface(title: str = '')`](#mininterfacetitle-str--)
    + [`alert(text: str)`](#alerttext-str)
    + [`ask(text: str) -> str`](#asktext-str---str)
    + [`ask_env() -> EnvInstance`](#ask_env--configinstance)
    + [`ask_number(text: str) -> int`](#ask_numbertext-str---int)
    + [`form(env: FormDict, title="") -> int`](#formenv-formdict-title---dict)
    + [`get_env(ask_on_empty_cli=True) -> ~EnvInstance`](#get_envask_on_empty_clitrue---configinstance)
    + [`is_no(text: str) -> bool`](#is_notext-str---bool)
    + [`is_yes(text: str) -> bool`](#is_yestext-str---bool)
    + [`parse_env(config: Type[EnvInstance], config_file: pathlib.Path | None = None, **kwargs) -> EnvInstance`](#parse_envconfig-type-configinstance-config_file-pathlibpath--none--none-kwargs---configinstance)
  * [Standalone](#standalone)

# Background

Wrapper between the [tyro](https://github.com/brentyi/tyro) `argparse` replacement and [tkinter_form](https://github.com/JohanEstebanCuervo/tkinter_form/) that converts dicts into a GUI.

Writing a small and useful program might be a task that takes fifteen minutes. Adding a CLI to specify the parameters is not so much overhead. But building a simple GUI around it? HOURS! Hours spent on researching GUI libraries, wondering why the Python desktop app ecosystem lags so far behind the web world. All you need is a few input fields validated through a clickable window... You do not deserve to add hundred of lines of the code just to define some editable fields. `Mininterface` is here to help.

The config variables needed by your program are kept in cozy dataclasses. Write less! The syntax of [tyro](https://github.com/brentyi/tyro) does not require any overhead (as its `argparse` alternatives do). You just annotate a class attribute, append a simple docstring and get a fully functional application:
* Call it as `program.py --help` to display full help.
* Use any flag in CLI: `program.py --test`  causes `env.test` be set to `True`.
* The main benefit: Launch it without parameters as `program.py` to get a full working window with all the flags ready to be edited.
* Running on a remote machine? Automatic regression to the text interface.

# Installation

Install with a single command from [PyPi](https://pypi.org/project/mininterface/).

```bash
pip install mininterface
```

# Docs

You can easily nest the configuration. (See also [Tyro Hierarchical Configs](https://brentyi.github.io/tyro/examples/02_nesting/01_nesting/)).

Just put another dataclass inside the config file:

```python3
@dataclass
class FurtherConfig:
    token: str
    host: str = "example.org"

@dataclass
class Config:
    further: FurtherConfig

...
print(config.further.host)  # example.org
```

A subset might be defaulted in YAML:

```yaml
further:
  host: example.com
```

Or by CLI:

```
$./program.py --further.host example.net
```

## `mininterface`

### `run(config=None, interface=GuiInterface, **kwargs)`
Wrap your configuration dataclass into `run` to access the interface. Normally, an interface is chosen automatically. We prefer the graphical one, regressed to a text interface on a machine without display.
Besides, if given a configuration dataclass, the function enriches it with the CLI commands and possibly with the default from a config file if such exists. It searches the config file in the current working directory, with the program name ending on *.yaml*, ex: `program.py` will fetch `./program.yaml`.

* `config:Type[EnvInstance]`: Dataclass with the configuration.
* `interface`: Which interface to prefer. By default, we use the GUI, the fallback is the REPL.
* `**kwargs`: The same as for [`argparse.ArgumentParser`](https://docs.python.org/3/library/argparse.html#argparse.ArgumentParser).
* Returns: `interface` Interface used.

You cay context manage the function by a `with` statement. The stdout will be redirected to the interface (GUI window).

See the [initial examples](#mininterface-gui-tui-cli-and-config).

## Interfaces

Several interfaces exist:

* `Mininterface` – The base interface. Does not require any user input and hence is suitable for headless testing.
* `GuiInterface` – A tkinter window.
* `TuiInterface` – An interactive terminal.
  * `TextualInterface` – If [textual](https://github.com/Textualize/textual) installed, rich interface is used.
  * `TextInterface` – Plain text only interface with no dependency as a fallback.
* `ReplInterface` – A debug terminal. Invokes a breakpoint after every dialog.

You can invoke one directly instead of using [mininterface.run](#run-config-none-interface-guiinterface-kwargs). Then, you can connect a configuration object to the CLI and config file with `parse_env` if needed.

```python
with TuiInterface("My program") as m:
    number = m.ask_number("Returns number")
```

### `Mininterface(title: str = '')`
Initialize.
### `alert(text: str)`
Prompt the user to confirm the text.
### `ask(text: str) -> str`
Prompt the user to input a text.
### `ask_env() -> EnvInstance`
Allow the user to edit whole configuration. (Previously fetched from CLI and config file by parse_env.)
### `ask_number(text: str) -> int`
Prompt the user to input a number. Empty input = 0.
### `form(env: FormDict, title="") -> dict`
Prompt the user to fill up whole form.
* `env`: Dict of `{labels: default value}`. The form widget infers from the default value type.
  The dict can be nested, it can contain a subgroup.
  The default value might be `mininterface.FormField` that allows you to add descriptions.
  A checkbox example: `{"my label": FormField(True, "my description")}`
* `title`: Optional form title.
### `get_env(ask_on_empty_cli=True) -> EnvInstance`
Returns whole configuration (previously fetched from CLI and config file by parse_env).
If program was launched with no arguments (empty CLI), invokes self.ask_env() to edit the fields.
### `is_no(text: str) -> bool`
Display confirm box, focusing no.
### `is_yes(text: str) -> bool`
Display confirm box, focusing yes.

```python
m = run(prog="My program")
print(m.ask_yes("Is it true?"))  # True/False
```

### `parse_env(config: Type[EnvInstance], config_file: pathlib.Path | None = None, **kwargs) -> ~EnvInstance`
Parse CLI arguments, possibly merged from a config file.
* `config`: Dataclass with the configuration.
* `config_file`: File to load YAML to be merged with the configuration. You do not have to re-define all the settings, you can choose a few.
* `**kwargs` The same as for argparse.ArgumentParser.
* Returns: `EnvInstance` Configuration namespace.

## Standalone

When invoked directly, it creates simple GUI dialogs.

```bash
$ mininterface  --help
usage: Mininterface [-h] [OPTIONS]

Simple GUI dialog. Outputs the value the user entered.

╭─ options ─────────────────────────────────────────────────────────────────────────────────╮
│ -h, --help              show this help message and exit                                   │
│ --alert STR             Display the OK dialog with text. (default: '')                    │
│ --ask STR               Prompt the user to input a text. (default: '')                    │
│ --ask-number STR        Prompt the user to input a number. Empty input = 0. (default: '') │
│ --is-yes STR            Display confirm box, focusing yes. (default: '')                  │
│ --is-no STR             Display confirm box, focusing no. (default: '')                   │
╰───────────────────────────────────────────────────────────────────────────────────────────╯
```

You can fetch a value to i.e. a bash script.

```bash
$ mininterface  --ask-number "What's your age?"  # GUI window invoked
18
```

