diff --git a/404.html b/404.html index 80cb3ea..eac943f 100644 --- a/404.html +++ b/404.html @@ -247,6 +247,43 @@ + + + + +
  • + + + + + + + + + + + + +
  • + + @@ -327,6 +417,43 @@ + + + + +
  • + + + + + + + + + + + + +
  • + + @@ -484,7 +664,7 @@

    404 - Not found

    - + diff --git a/Changelog/index.html b/Changelog/index.html index 9fd13bf..5d7c3ea 100644 --- a/Changelog/index.html +++ b/Changelog/index.html @@ -61,6 +61,11 @@
    + + + Skip to content + +
    @@ -249,6 +254,43 @@ + + + + +
  • + + + + + + + + + + + + +
  • + + @@ -329,6 +424,43 @@ + + + + +
  • + + + + + + + + + + + + +
  • + + @@ -416,6 +601,19 @@ + + + + @@ -427,6 +625,34 @@ + + + + @@ -448,6 +674,25 @@ + + + + +
    @@ -465,9 +710,13 @@ -

    Changelog

    - -

    Due to an early stage of development, changes are tracked in commit messages only.

    +

    Changelog

    +

    0.7.0

    + @@ -514,7 +763,7 @@

    Changelog

    - + diff --git a/Exceptions/index.html b/Exceptions/index.html new file mode 100644 index 0000000..4f6e93e --- /dev/null +++ b/Exceptions/index.html @@ -0,0 +1,918 @@ + + + + + + + + + + + + + + + + + + + + + + + Exceptions - Mininterface + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + Skip to content + + +
    +
    + +
    + + + + + + +
    + + +
    + +
    + + + + + + +
    +
    + + + +
    +
    +
    + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    + + + + + + + +

    Exceptions

    + + +
    + + + + +
    + +

    Exceptions that might make sense to be used outside the library.

    + + + +
    + + + + + + + + +
    + + + +

    + Cancelled + + +

    + + +
    + + +

    User has cancelled. +A SystemExit based exception noting that the program exits without a traceback, +ex. if user hits the escape or closes the window.

    + + +
    + +
    + +
    + + + +

    + ValidationFail + + +

    + + +
    + + +

    Signal to the form that submit failed and we want to restore it.

    + + +
    + +
    + +
    + + + +

    + InterfaceNotAvailable + + +

    + + +
    + + +

    Interface failed to init, ex. display not available in GUI. Or the underlying dependency was uninstalled.

    + + +
    + +
    + + + + +
    + +
    + +
    + + + + + + + + + + + + + +
    +
    + + + +
    + +
    + + + +
    +
    +
    +
    + + + + + + + + + + \ No newline at end of file diff --git a/Experimental/index.html b/Experimental/index.html index 5eb45fe..f042d1e 100644 --- a/Experimental/index.html +++ b/Experimental/index.html @@ -256,6 +256,43 @@ + + + + +
  • + + + + + + + + + + + + +
  • + + + + + + + + + + +
  • + + + + + + + + + + +
  • + + @@ -670,7 +850,7 @@

    - + diff --git a/Facet/index.html b/Facet/index.html index 5580533..5cfa177 100644 --- a/Facet/index.html +++ b/Facet/index.html @@ -12,7 +12,7 @@ - + @@ -254,6 +254,43 @@ + + + + + + + + +
  • + + + + + + + + + + +
  • + + @@ -418,6 +508,43 @@ + + + + +
  • + + + + + + + + + + + + +
  • + + @@ -723,7 +903,7 @@

    {"base": "..", "features": [], "search": "../assets/javascripts/workers/search.07f07601.min.js", "translations": {"clipboard.copied": "Copied to clipboard", "clipboard.copy": "Copy to clipboard", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.placeholder": "Type to start searching", "search.result.term.missing": "Missing", "select.version": "Select version"}} + diff --git a/Helper-types/index.html b/Helper-types/index.html new file mode 100644 index 0000000..726a1b1 --- /dev/null +++ b/Helper-types/index.html @@ -0,0 +1,965 @@ + + + + + + + + + + + + + + + + + + + + + + + Helper types - Mininterface + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + Skip to content + + +
    +
    + +
    + + + + + + +
    + + +
    + +
    + + + + + + +
    +
    + + + +
    +
    +
    + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    + + + + + + + +

    Helper types

    + + +
    + + + +

    + ValidationResult = bool | ErrorMessage + +

    + + +
    + +

    Callback validation result is either boolean or an error message.

    +
    + +
    + +
    + + + +

    + ErrorMessage = TypeVar('ErrorMessage') + +

    + + +
    + +

    A string, callback validation error message.

    +
    + +
    + +
    + + + +

    + TagValue = TypeVar('TagValue', bound=Any) + +

    + + +
    + +

    Any value. It is being wrapped by a Tag.

    +
    + +
    + +
    + + + +

    + ChoicesType = list[TagValue] | tuple[TagValue] | set[TagValue] | dict[ChoiceLabel, TagValue] | list[Enum] | Type[Enum] + +

    + + +
    + +

    You can denote the choices in many ways. +Either put options in an iterable or to a dict {labels: value}. +Values might be Tags as well.

    +

    See mininterface.choice or Tag.choices for examples.

    +
    + +
    + +
    + + + +

    + DataClass = TypeVar('DataClass') + +

    + + +
    + +

    Any dataclass. Or a pydantic model or attrs.

    +
    + +
    + +
    + + + +

    + EnvClass = TypeVar('EnvClass', bound=DataClass) + +

    + + +
    + +

    Any dataclass. Its instance will be available through [miniterface.env] after CLI parsing.

    +
    + +
    + + + + + + + + + + + + + +
    +
    + + + +
    + +
    + + + +
    +
    +
    +
    + + + + + + + + + + \ No newline at end of file diff --git a/Interfaces/index.html b/Interfaces/index.html new file mode 100644 index 0000000..6783999 --- /dev/null +++ b/Interfaces/index.html @@ -0,0 +1,884 @@ + + + + + + + + + + + + + + + + + + + + + + + Interfaces - Mininterface + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + Skip to content + + +
    +
    + +
    + + + + + + +
    + + +
    + +
    + + + + + + +
    +
    + + + +
    +
    +
    + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    + + + + + + + +

    All possible interfaces

    +

    Normally, you get an interface through mininterface.run +but if you do not wish to parse CLI and config file, you can invoke one directly from mininterface.interfaces import *.

    +

    Apart from the default Mininterface, the base interface the others are fully compatible with, several interfaces exist:

    +

    How to invoke a specific interface.

    +
    from mininterface.interfaces import TuiInterface
    +
    +with TuiInterface("My program") as m:
    +    number = m.ask_number("Returns number")
    +
    +

    GuiInterface = TkInterface

    +

    A tkinter window.

    +

    TuiInterface

    +

    An interactive terminal.

    +

    TextualInterface

    +

    If textual installed, rich and mouse clickable interface is used.

    +

    TextInterface

    +

    Plain text only interface with no dependency as a fallback.

    +

    ReplInterface

    +

    A debug terminal. Invokes a breakpoint after every dialog.

    + + + + + + + + + + + + + +
    +
    + + + +
    + +
    + + + +
    +
    +
    +
    + + + + + + + + + + \ No newline at end of file diff --git a/Mininterface/index.html b/Mininterface/index.html index d8db4b4..b2fe778 100644 --- a/Mininterface/index.html +++ b/Mininterface/index.html @@ -254,6 +254,43 @@ + + + + + + + + +
  • + + + + + + + + + + +
  • + + @@ -475,6 +565,43 @@ + + + + +
  • + + + + + + + + + + + + +
  • + + @@ -725,17 +905,17 @@

    Mininterface

    The base interface. You get one through mininterface.run which fills CLI arguments and config file to mininterface.env - or you can create one directly (without benefiting from the CLI parsing).

    + or you can create one directly (without benefiting from the CLI parsing).

    Raise -

    Cancelled: A SystemExit based exception noting that the program exits without a traceback, ex. if user hits the escape.

    +

    Cancelled: A SystemExit based exception noting that the program exits without a traceback, ex. if user hits the escape.

    Raise -

    InterfaceNotAvailable: Interface failed to init, ex. display not available in GUI.

    +

    InterfaceNotAvailable: Interface failed to init, ex. display not available in GUI.

    @@ -958,7 +1138,7 @@

    choices - ChoicesType + ChoicesType
    @@ -1021,7 +1201,7 @@

    default - str | TagValue | None + str | TagValue | None
    @@ -1078,7 +1258,7 @@

    - TagValue | list[TagValue] | Any + TagValue | list[TagValue] | Any
    @@ -1088,7 +1268,7 @@

    - TagValue | list[TagValue] | Any + TagValue | list[TagValue] | Any
    @@ -1156,7 +1336,7 @@

    form - DataClass | Type[DataClass] | FormDict | None + DataClass | Type[DataClass] | FormDict | None
    @@ -1221,7 +1401,7 @@

    dataclass - FormDict | DataClass | EnvClass + FormDict | DataClass | EnvClass
    @@ -1231,7 +1411,7 @@

    dataclass - FormDict | DataClass | EnvClass + FormDict | DataClass | EnvClass
    @@ -1241,7 +1421,7 @@

    dict - FormDict | DataClass | EnvClass + FormDict | DataClass | EnvClass
    @@ -1374,7 +1554,7 @@

    - + diff --git a/Overview/index.html b/Overview/index.html index 7738faa..b204c12 100644 --- a/Overview/index.html +++ b/Overview/index.html @@ -160,22 +160,36 @@
  • - + - Nested configuration + Bash completion
  • - + - All possible interfaces + Nested configuration
  • +
  • + + + +
  • +
  • + + + +
  • @@ -268,16 +330,16 @@
  • - + - Nested configuration + Bash completion
  • - + - All possible interfaces + Nested configuration
  • @@ -302,7 +364,7 @@

    Overview

    Basic usage

    Use a common dataclass, a Pydantic BaseModel or an attrs model to store the configuration. Wrap it to the run function that returns an interface m. Access the configuration via m.env or use it to prompt the user m.is_yes("Is that alright?").

    -

    There are a lot of supported types you can use, not only scalars and well-known objects (Path, datetime), but also functions, iterables (like list[Path]) and union types (like int | None). To do even more advanced things, stick the value to a powerful Tag or its subclasses. Ex. for a validation only, use its Validation alias.

    +

    There are a lot of supported types you can use, not only scalars and well-known objects (Path, datetime), but also functions, iterables (like list[Path]) and union types (like int | None). To do even more advanced things, stick the value to a powerful Tag or its subclasses. Ex. for a validation only, use its Validation alias.

    At last, use Facet to tackle the interface from the back-end (m) or the front-end (Tag) side.

    IDE suggestions

    The immediate benefit is the type suggestions you see in an IDE. Imagine following code:

    @@ -334,6 +396,9 @@

    IDE suggestions

    Suggestion dataclass expanded

    Should the dataclass cannot be easily investigated by the IDE (i.e. a required field), just annotate the output.

    Suggestion annotation possible

    +

    Bash completion

    +

    Run your program with a hidden --integrate-to-system flag and a tutorial will install bash completion.

    +

    Bash completion

    Nested configuration

    You can easily nest the configuration. (See also Tyro Hierarchical Configs).

    Just put another dataclass inside the config file:

    @@ -358,22 +423,6 @@

    Nested configuration

    further:
       host: example.com
     
    -

    All possible interfaces

    -

    Normally, you get an interface through mininterface.run -but if you do not wish to parse CLI and config file, you can invoke one directly.

    -

    Several interfaces exist:

    -
      -
    • Mininterface – The base interface the others are fully compatible with.
    • -
    • GuiInterface = TkInterface – A tkinter window.
    • -
    • TuiInterface – An interactive terminal.
    • -
    • TextualInterface – If 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.
    • -
    -

    How to invoke a specific interface.

    -
    with TuiInterface("My program") as m:
    -    number = m.ask_number("Returns number")
    -
    @@ -397,7 +446,7 @@

    All possible interfaces

    - + + diff --git a/Subcommands/index.html b/Subcommands/index.html new file mode 100644 index 0000000..1117dba --- /dev/null +++ b/Subcommands/index.html @@ -0,0 +1,1163 @@ + + + + + + + + + + + + + + + + + + + + + + + Subcommands - Mininterface + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + + + +
    + + +
    + +
    + + + + + + +
    +
    + + + +
    +
    +
    + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    + + + + + + + +

    Subcommands

    + + +
    + + + + +
    + +

    Dealing with CLI subcommands, from mininterface.subcommands import *

    + + + +
    + + + + + + + + +
    + + + +

    + Command + + +

    + + +
    + + +

    The Command is automatically run while instantanied.

    +

    Experimental – how should it receive _facet?

    +

    Put list of Commands to the mininterface.run and divide your application into different sections. +Alternative to argparse subcommands.

    +

    Commands might inherit from the same parent to share the common attributes.

    +

    SubcommandPlaceholder

    +

    What if I need to use my program +Special placeholder class SubcommandPlaceholder. +This special class let the user to choose the subcommands via UI, +while still benefiniting from default CLI arguments.

    +
    The CLI behaviour:
    +
      +
    • ./program.py -> UI started with subcommand choice
    • +
    • ./program.py subcommand --flag -> special class SubcommandPlaceholder allows defining a common --flag + while still starting UI with subcommand choice
    • +
    • ./program.py subcommand1 --flag -> program run
    • +
    • ./program.py subcommand1 -> fails to CLI for now
    • +
    +

    An example of Command usage

    +
    from dataclasses import dataclass, field
    +from pathlib import Path
    +from mininterface import run
    +from mininterface.exceptions import ValidationFail
    +from mininterface.subcommands import Command, SubcommandPlaceholder
    +from tyro.conf import Positional
    +
    +
    +@dataclass
    +class SharedArgs(Command):
    +    common: int
    +    files: Positional[list[Path]] = field(default_factory=list)
    +
    +    def init(self):
    +        self.internal = "value"
    +
    +
    +@dataclass
    +class Subcommand1(SharedArgs):
    +    my_local: int = 1
    +
    +    def run(self):
    +        print("Common:", self.common)  # user input
    +        print("Number:", self.my_local)  # 1 or user input
    +        print("Internal:", self.internal)
    +        raise ValidationFail("The submit button blocked!")
    +
    +
    +@dataclass
    +class Subcommand2(SharedArgs):
    +    def run(self):
    +        self._facet.set_title("Button clicked")  # you can access internal self._facet: Facet
    +        print("Common files", self.files)
    +
    +
    +m = run([Subcommand1, Subcommand2, SubcommandPlaceholder])
    +m.alert("App continue")
    +
    +

    Let's start the program, passing there common flags, all HTML files in a folder and setting --common to 7. +

    $ program.py  subcommand *.html  --common 7
    +

    +

    Subcommand

    +

    As you see, thanks to SubcommandPlaceholder, subcommand was not chosen yet. Click to the first button.

    +

    Subcommand

    +

    and the terminal got:

    +
    Common: 7
    +Number: 1
    +Internal: value
    +
    +

    Click to the second button.

    +

    Subcommand

    +

    Terminal output: +

    Common files [PosixPath('page1.html'), PosixPath('page2.html')]
    +

    +
    Powerful automation
    +

    Note we use from tyro.conf import Positional to denote the positional argument. We did not have to write --files to put there HTML files.

    + + + + +
    + + + + + + + + + +
    + + +

    + init() + +

    + + +
    + +

    Just before the form appears. +As the __post_init__ method is not guaranteed to run just once (internal CLI behaviour), +you are welcome to override this method instead. You can use self._facet from within.

    + +
    + +
    + +
    + + +

    + run() + +

    + + +
    + +

    This method is run automatically in CLI or by a button button it generates in a UI.

    + +
    + +
    + + + +
    + +
    + +
    + +
    + + + +

    + SubcommandPlaceholder + + +

    + + +
    + + +

    Use this placeholder to choose the subcomannd via a UI.

    + + + + +
    + + + + + + + + + + + +
    + +
    + +
    + + + + +
    + +
    + +
    + + + + + + + + + + + + + +
    +
    + + + +
    + +
    + + + +
    +
    +
    +
    + + + + + + + + + + \ No newline at end of file diff --git a/Tag/index.html b/Tag/index.html index 38dd176..8d6d94f 100644 --- a/Tag/index.html +++ b/Tag/index.html @@ -254,6 +254,43 @@ + + + + + + + + +
  • + + + + + + + + +
  • + -
  • - - - ValidationResult - - +
  • - - -
  • - - - ErrorMessage - - + + + + + -
  • - -
  • - - - TagValue - - -
  • - -
  • - - - ChoicesType - - -
  • - - - - +
  • + + + + + Facet + + + +
  • - - - + + + +
  • - + - Facet + Subcommands + + + + +
  • + + + + + + + + + + +
  • + + + + + Helper types @@ -531,6 +568,14 @@
  • + + + + + + + + @@ -538,6 +583,43 @@ + + + + +
  • + + + + + + + + + + + + +
  • + + @@ -773,51 +908,6 @@ - - -
  • - - - Helper types - - - -
  • - -
  • - - - ValidationResult - - - -
  • - -
  • - - - ErrorMessage - - - -
  • - -
  • - - - TagValue - - - -
  • - -
  • - - - ChoicesType - - -
  • @@ -967,7 +1057,7 @@

    When the user submits the form, the values are validated (and possibly transformed) with a callback function. If the validation fails, user is prompted to edit the value. Return True if validation succeeded or False or an error message when it failed.

    -

    ValidationResult is a bool or the error message (that implicitly means it has failed).

    +

    ValidationResult is a bool or the error message (that implicitly means it has failed).

    def check(tag: Tag):
         if tag.val < 10:
             return "The value must be at least 10"
    @@ -1158,7 +1248,7 @@ 

    ui_value - TagValue + TagValue
    @@ -1226,78 +1316,6 @@

    -

    Helper types

    - - -
    - - - -

    - ValidationResult = bool | ErrorMessage - -

    - - -
    - -

    Callback validation result is either boolean or an error message.

    -
    - -
    - -
    - - - -

    - ErrorMessage = TypeVar('ErrorMessage') - -

    - - -
    - -

    A string, callback validation error message.

    -
    - -
    - -
    - - - -

    - TagValue = TypeVar('TagValue') - -

    - - -
    - -

    Any value.

    -
    - -
    - -
    - - - -

    - ChoicesType = list[TagValue] | tuple[TagValue] | set[TagValue] | dict[ChoiceLabel, TagValue] | list[Enum] | Type[Enum] - -

    - - -
    - -

    You can denote the choices in many ways. -Either put options in an iterable or to a dict {labels: value}. -Values might be Tags as well.

    -

    See mininterface.choice or Tag.choices for examples.

    -
    -
    @@ -1345,7 +1363,7 @@

    - + diff --git a/Types/index.html b/Types/index.html index 2d36c50..d0b378c 100644 --- a/Types/index.html +++ b/Types/index.html @@ -9,7 +9,7 @@ - + @@ -256,6 +256,43 @@ + + + + +
  • + + + + + + + + + + + + +
  • + + @@ -338,6 +428,43 @@ + + + + +
  • + + + + + + + + + + +
  • + + @@ -914,7 +1094,7 @@

    check - Callable[[Tag], ValidationResult | tuple[ValidationResult, TagValue]] + Callable[[Tag], ValidationResult | tuple[ValidationResult, TagValue]]
    @@ -1002,7 +1182,7 @@

    - + diff --git a/Validation/index.html b/Validation/index.html index 20804ad..a9ea7a1 100644 --- a/Validation/index.html +++ b/Validation/index.html @@ -12,7 +12,7 @@ - + @@ -256,6 +256,43 @@ + + + + +
  • + + + + + + + + + + + + +
  • + + + + + + + + + + +
  • + + + + + + + + + + +
  • + + @@ -795,7 +975,7 @@

    -

    If the value is not withing the limit, transform it to a boundary. +

    If the value is not within the limit, transform it to a boundary.

    from mininterface import run, Tag
     from mininterface.validators import limit
     
    @@ -871,7 +1051,7 @@ 

    - + diff --git a/asset/bash_completion_tutorial.avif b/asset/bash_completion_tutorial.avif new file mode 100644 index 0000000..6a3c2b2 Binary files /dev/null and b/asset/bash_completion_tutorial.avif differ diff --git a/asset/hello-gui.avif b/asset/hello-gui.avif new file mode 100644 index 0000000..7455700 Binary files /dev/null and b/asset/hello-gui.avif differ diff --git a/asset/hello-tui.avif b/asset/hello-tui.avif new file mode 100644 index 0000000..1fc51da Binary files /dev/null and b/asset/hello-tui.avif differ diff --git a/asset/hello-tui.webp b/asset/hello-tui.webp deleted file mode 100644 index 1829aa2..0000000 Binary files a/asset/hello-tui.webp and /dev/null differ diff --git a/asset/hello-world.png b/asset/hello-world.png deleted file mode 100644 index a308b1c..0000000 Binary files a/asset/hello-world.png and /dev/null differ diff --git a/asset/subcommands-1.avif b/asset/subcommands-1.avif new file mode 100644 index 0000000..e047934 Binary files /dev/null and b/asset/subcommands-1.avif differ diff --git a/asset/subcommands-2.avif b/asset/subcommands-2.avif new file mode 100644 index 0000000..2f4b458 Binary files /dev/null and b/asset/subcommands-2.avif differ diff --git a/asset/subcommands-3.avif b/asset/subcommands-3.avif new file mode 100644 index 0000000..c9beabb Binary files /dev/null and b/asset/subcommands-3.avif differ diff --git a/index.html b/index.html index 2f08227..6789e87 100644 --- a/index.html +++ b/index.html @@ -264,6 +264,43 @@ + + + + +
  • + + + + + + + + + + + + +
  • + + @@ -344,6 +434,43 @@ + + + + +
  • + + + + + + + + + + + + +
  • + + @@ -477,10 +657,10 @@

    Mininterface – access to GUI, TUI, CLI and config files

    License: GPL v3 Build Status -Downloads

    +Downloads

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

    -

    Hello world example: GUI window -Hello world example: TUI fallback

    +

    Hello world example: GUI window +Hello world example: TUI fallback

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

    from dataclasses import dataclass
     from mininterface import run
    @@ -496,10 +676,11 @@ 

    Mininterface – ac """ This number is very important """ if __name__ == "__main__": - env = run(Env, prog="My application").env - # Attributes are suggested by the IDE - # along with the hint text 'This number is very important'. - print(env.my_number) + m = run(Env, prog="My application") + m.form() + # Attributes are suggested by the IDE + # along with the hint text 'This number is very important'. + print(m.env.my_number)

    Contents

      @@ -513,16 +694,18 @@

      Contents

    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.

    -
    $ ./hello.py
    -usage: My application [-h] [--test | --no-test] [--important-number INT]
    +
    $ ./hello.py    --help
    +usage: My application [-h] [-v] [--my-flag | --no-my-flag] [--my-number INT]
     
     This calculates something.
     
    -╭─ 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) │
    -╰────────────────────────────────────────────────────────────────────╯
    +╭─ 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)       │
    +╰─────────────────────────────────────────────────────────────────────────╯
     

    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.

    @@ -548,65 +731,71 @@

    Installation

    Install with a single command from PyPi.

    pip install mininterface
     
    +

    Minimal installation

    +

    Should you need just the CLI part and you are happy with basic text dialogs, use these commands instead:

    +
    pip install --no-dependencies mininterface
    +pip install tyro typing_extensions pyyaml
    +

    Docs

    See the docs overview at https://cz-nic.github.io/mininterface/.

    Examples

    +

    A powerful m.form dialog method accepts either a dataclass or a dict. Take a look on both.

    A complex dataclass.

    -
    from typing import Annotated
    -from dataclasses import dataclass
    -from mininterface.validators import not_empty
    -from mininterface import run, Tag, Validation
    -
    -@dataclass
    -class NestedEnv:
    -  another_number: int = 7
    -  """ This field is nested """
    -
    -@dataclass
    -class Env:
    -  nested_config: NestedEnv
    -
    -  mandatory_str: str
    -  """ As there is no default value, you will be prompted automatically to fill up the field """
    -
    -  my_number: int | None = None
    -  """ This is not just a dummy number, if left empty, it is None. """
    -
    -  my_string: str = "Hello"
    -  """ A dummy string """
    -
    -  my_flag: bool = False
    -  """ Checkbox test """
    -
    -  my_validated: Annotated[str, Validation(not_empty)] = "hello"
    -  """ A validated field """
    -
    -m = run(Env, title="My program")
    -# See some values
    -print(m.env.nested_config.another_number)  # 7
    -print(m.env)
    -# Env(nested_config=NestedEnv(another_number=7), my_number=5, my_string='Hello', my_flag=False, my_validated='hello')
    -
    -# Edit values in a dialog
    -m.form()
    +
    from typing import Annotated
    +from dataclasses import dataclass
    +from mininterface.validators import not_empty
    +from mininterface import run, Tag, Validation
    +
    +@dataclass
    +class NestedEnv:
    +  another_number: int = 7
    +  """ This field is nested """
    +
    +@dataclass
    +class Env:
    +  nested_config: NestedEnv
    +
    +  mandatory_str: str
    +  """ As there is no default value, you will be prompted automatically to fill up the field """
    +
    +  my_number: int | None = None
    +  """ This is not just a dummy number, if left empty, it is None. """
    +
    +  my_string: str = "Hello"
    +  """ A dummy string """
    +
    +  my_flag: bool = False
    +  """ Checkbox test """
    +
    +  my_validated: Annotated[str, Validation(not_empty)] = "hello"
    +  """ A validated field """
    +
    +m = run(Env, title="My program")
    +# See some values
    +print(m.env.nested_config.another_number)  # 7
    +print(m.env)
    +# Env(nested_config=NestedEnv(another_number=7), my_number=5, my_string='Hello', my_flag=False, my_validated='hello')
    +
    +# Edit values in a dialog
    +m.form()
     
    -

    As there is no default value at mandatory_str, you will be prompted automatically to fill up the field:

    +

    As the attribute mandatory_str requires a value, a prompt appears automatically:

    Complex example missing field

    Then, full form appears:

    Complex example

    Form with paths

    We have a dict with some paths. Here is how it looks.

    -
    from pathlib import Path
    -from mininterface import run, Tag
    -
    -m = run(title="My program")
    -my_dictionary = {
    -  "paths": Tag("", annotation=list[Path]),
    -  "default_paths": Tag([Path("/tmp"), Path("/usr")], annotation=list[Path])
    -  }
    -
    -# Edit values in a dialog
    -m.form(my_dictionary)
    +
    from pathlib import Path
    +from mininterface import run, Tag
    +
    +m = run(title="My program")
    +my_dictionary = {
    +  "paths": Tag("", annotation=list[Path]),
    +  "default_paths": Tag([Path("/tmp"), Path("/usr")], annotation=list[Path])
    +  }
    +
    +# Edit values in a dialog
    +m.form(my_dictionary)
     

    List of paths

    @@ -655,7 +844,7 @@

    Form with paths

    - + diff --git a/objects.inv b/objects.inv index 475e254..c4cce5e 100644 Binary files a/objects.inv and b/objects.inv differ diff --git a/run/index.html b/run/index.html index 99fdb12..55b3dc1 100644 --- a/run/index.html +++ b/run/index.html @@ -258,6 +258,43 @@ + + + + +
  • + + + + + + + + + + +
  • + + @@ -381,6 +471,43 @@ + + + + +
  • + + + + + + + + + + + + +
  • + + @@ -556,13 +736,17 @@

    Run

    - env_class + env_or_list - Type[EnvClass] | None + Type[EnvClass] | list[Type[Command]] | None
    -

    Dataclass with the configuration. Their values will be modified with the CLI arguments.

    +
      +
    • dataclass Dataclass with the configuration. Their values will be modified with the CLI arguments.
    • +
    • list of Commands let you create multiple commands within a single program, each with unique options.
    • +
    • None You need just the dialogs, no CLI/config file parsing.
    • +
    @@ -679,26 +863,39 @@

    Run

    interface - Type[Mininterface] + Type[Mininterface] | Literal['gui'] | Literal['tui'] + + +
    +

    Which interface to prefer. By default, we use the GUI, the fallback is the TUI. +You may write "gui" or "tui" literal or pass a specific Mininterface type, +see the full list of possible interfaces.

    +
    + + + None + + + + args + + Optional[Sequence[str]]
    -

    Which interface to prefer. By default, we use the GUI, the fallback is the TUI. See the full list of possible interfaces.

    +

    Parse arguments from a sequence instead of the command line.

    - GuiInterface or TuiInterface + None +

    Kwargs: + The same as for argparse.ArgumentParser.

    -
    - Kwargs -

    The same as for argparse.ArgumentParser.

    -
    -

    Returns:

    @@ -710,7 +907,7 @@

    Run

    - Mininterface[EnvClass] + Mininterface[EnvClass]
    @@ -722,9 +919,16 @@

    Run

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

    -
    with run(Env) as m:
    -    print(f"Your important number is {m.env.important_number}")
    -    boolean = m.is_yes("Is that alright?")
    +
    from dataclasses import dataclass
    +from mininterface import run
    +
    +@dataclass
    +class Env:
    +    my_number: int = 4
    +
    +with run(Env) as m:
    +    print(f"Your important number is {m.env.my_number}")
    +    boolean = m.is_yes("Is that alright?")
     

    Small window with the text 'Your important number' The same in terminal'

    @@ -778,7 +982,7 @@

    Run

    - + diff --git a/search/search_index.json b/search/search_index.json index 83157d7..a02a9ab 100644 --- a/search/search_index.json +++ b/search/search_index.json @@ -1 +1 @@ -{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"","title":"Mininterface \u2013 access to GUI, TUI, CLI and config files","text":"

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

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

    from dataclasses import dataclass\nfrom mininterface import run\n\n@dataclass\nclass Env:\n    \"\"\" This calculates something. \"\"\"\n\n    my_flag: bool = False\n    \"\"\" This switches the functionality \"\"\"\n\n    my_number: int = 4\n    \"\"\" This number is very important \"\"\"\n\nif __name__ == \"__main__\":\n    env = run(Env, prog=\"My application\").env\n    # Attributes are suggested by the IDE\n    # along with the hint text 'This number is very important'.\n    print(env.my_number)\n
    "},{"location":"#contents","title":"Contents","text":"
    • You got CLI
    • You got config file management
    • You got dialogues
    • Background
    • Installation
    • Docs
    • Examples
    "},{"location":"#you-got-cli","title":"You got CLI","text":"

    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.

    $ ./hello.py\nusage: My application [-h] [--test | --no-test] [--important-number INT]\n\nThis calculates something.\n\n\u256d\u2500 options \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256e\n\u2502 -h, --help              show this help message and exit            \u2502\n\u2502 --test, --no-test       My testing flag (default: False)           \u2502\n\u2502 --important-number INT  This number is very important (default: 4) \u2502\n\u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256f\n
    "},{"location":"#you-got-config-file-management","title":"You got config file management","text":"

    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.

    my_number: 555\n
    "},{"location":"#you-got-dialogues","title":"You got dialogues","text":"

    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.

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

    "},{"location":"#background","title":"Background","text":"

    Wrapper between the tyro argparse replacement and 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 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 --my-flag causes env.my_flag 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.

    "},{"location":"#installation","title":"Installation","text":"

    Install with a single command from PyPi.

    pip install mininterface\n
    "},{"location":"#docs","title":"Docs","text":"

    See the docs overview at https://cz-nic.github.io/mininterface/.

    "},{"location":"#examples","title":"Examples","text":""},{"location":"#a-complex-dataclass","title":"A complex dataclass.","text":"
    from typing import Annotated\nfrom dataclasses import dataclass\nfrom mininterface.validators import not_empty\nfrom mininterface import run, Tag, Validation\n\n@dataclass\nclass NestedEnv:\n  another_number: int = 7\n  \"\"\" This field is nested \"\"\"\n\n@dataclass\nclass Env:\n  nested_config: NestedEnv\n\n  mandatory_str: str\n  \"\"\" As there is no default value, you will be prompted automatically to fill up the field \"\"\"\n\n  my_number: int | None = None\n  \"\"\" This is not just a dummy number, if left empty, it is None. \"\"\"\n\n  my_string: str = \"Hello\"\n  \"\"\" A dummy string \"\"\"\n\n  my_flag: bool = False\n  \"\"\" Checkbox test \"\"\"\n\n  my_validated: Annotated[str, Validation(not_empty)] = \"hello\"\n  \"\"\" A validated field \"\"\"\n\nm = run(Env, title=\"My program\")\n# See some values\nprint(m.env.nested_config.another_number)  # 7\nprint(m.env)\n# Env(nested_config=NestedEnv(another_number=7), my_number=5, my_string='Hello', my_flag=False, my_validated='hello')\n\n# Edit values in a dialog\nm.form()\n

    As there is no default value at mandatory_str, you will be prompted automatically to fill up the field:

    Then, full form appears:

    "},{"location":"#form-with-paths","title":"Form with paths","text":"

    We have a dict with some paths. Here is how it looks.

    from pathlib import Path\nfrom mininterface import run, Tag\n\nm = run(title=\"My program\")\nmy_dictionary = {\n  \"paths\": Tag(\"\", annotation=list[Path]),\n  \"default_paths\": Tag([Path(\"/tmp\"), Path(\"/usr\")], annotation=list[Path])\n  }\n\n# Edit values in a dialog\nm.form(my_dictionary)\n

    "},{"location":"Changelog/","title":"Changelog","text":"

    Due to an early stage of development, changes are tracked in commit messages only.

    "},{"location":"Experimental/","title":"Experimental","text":""},{"location":"Experimental/#mininterface.experimental.SubmitButton","title":"SubmitButton","text":"

    Create a button. When clicked, the form submits. If submission succeeds (validation checks pass), its value becomes True.

    from pathlib import Path\nfrom mininterface import run, Tag\nfrom mininterface.experimental import SubmitButton\n\nm = run()\nout = m.form({\n    \"File name\": Tag(\"/tmp\", annotation=Path),\n    \"Append text\": {\n        \"My text\": \"\",\n        \"Append now\": SubmitButton()\n    },\n    \"Duplicate\": {\n        \"Method\": Tag(\"twice\", choices=[\"twice\", \"thrice\"]),\n        \"Duplicate now\": SubmitButton()\n    }\n})\n# Clicking on 'Append now' button\nprint(out)\n# {'File name': PosixPath('/tmp'),\n# 'Append text': {'My text': '', 'Append now': True},\n# 'Duplicate': {'Method': 'twice', 'Duplicate now': False}}\n

    "},{"location":"Facet/","title":"Facet","text":"

    A frontend side of the interface. While a dialog is open, this allows to set frontend properties like the heading.

    Read Tag.facet to see how to access from the front-end side. Read Mininterface.facet to see how to access from the back-end side.

    "},{"location":"Facet/#mininterface.facet.Facet.set_title","title":"set_title(text)","text":"

    Set the main heading.

    "},{"location":"Facet/#mininterface.facet.Facet.submit","title":"submit(_post_submit=None)","text":"

    Submits the whole form.

    ```python from mininterface import run, Tag

    def callback(tag: Tag): tag.facet.submit()

    m = run() out = m.form({ \"My choice\": Tag(choices=[\"one\", \"two\"], on_change=callback) })

    "},{"location":"Facet/#mininterface.facet.Facet.submit--continue-here-immediately-after-clicking-on-a-radio-button","title":"continue here immediately after clicking on a radio button","text":""},{"location":"Mininterface/","title":"Mininterface","text":"

    The base interface. You get one through mininterface.run which fills CLI arguments and config file to mininterface.env or you can create one directly (without benefiting from the CLI parsing).

    Raise

    Cancelled: A SystemExit based exception noting that the program exits without a traceback, ex. if user hits the escape.

    Raise

    InterfaceNotAvailable: Interface failed to init, ex. display not available in GUI.

    "},{"location":"Mininterface/#mininterface.Mininterface.env","title":"env: EnvClass = EnvInstance","text":"

    Parsed arguments, fetched from cli. Contains whole configuration (previously fetched from CLI and config file).

    $ program.py --number 10\n
    from dataclasses import dataclass\nfrom mininterface import run\n\n@dataclass\nclass Env:\n    number: int = 3\n    text: str = \"\"\n\nm = run(Env)\nprint(m.env.number)  # 10\n
    "},{"location":"Mininterface/#mininterface.Mininterface.facet","title":"facet = Facet(None, self.env)","text":"

    Access to the UI facet from the back-end side. (Read Tag.facet to access from the front-end side.)

    from mininterface import run\nwith run(title='My window title') as m:\n    m.facet.set_title(\"My form title\")\n    m.form({\"My form\": 1})\n

    "},{"location":"Mininterface/#mininterface.Mininterface.__enter__","title":"__enter__()","text":"

    When used in the with statement, the GUI window does not vanish between dialogs and it redirects the stdout to a text area.

    "},{"location":"Mininterface/#mininterface.Mininterface.alert","title":"alert(text)","text":"

    Prompt the user to confirm the text.

    "},{"location":"Mininterface/#mininterface.Mininterface.ask","title":"ask(text)","text":"

    Prompt the user to input a text.

    "},{"location":"Mininterface/#mininterface.Mininterface.ask_number","title":"ask_number(text)","text":"

    Prompt the user to input a number. Empty input = 0.

    m = run()  # receives a Mininterface object\nm.ask_number(\"What's your age?\")\n

    Parameters:

    Name Type Description Default text str

    The question text.

    required

    Returns:

    Type Description int

    Number

    "},{"location":"Mininterface/#mininterface.Mininterface.choice","title":"choice(choices, title='', _guesses=None, skippable=True, launch=True, _multiple=True, default=None)","text":"

    Prompt the user to select. Useful for a menu creation.

    Parameters:

    Name Type Description Default choices ChoicesType

    You can denote the choices in many ways. Either put options in an iterable:

    from mininterface import run\nm = run()\nm.choice([1, 2])\n

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

    m.choice({\"one\": 1, \"two\": 2})  # returns 1\n

    Alternatively, you may specify the names in Tags.

    m.choice([Tag(1, name=\"one\"), Tag(2, name=\"two\")])  # returns 1\n

    Alternatively, you may use an Enum.

    class Color(Enum):\n    RED = \"red\"\n    GREEN = \"green\"\n    BLUE = \"blue\"\n\nm.choice(Color)\n

    Alternatively, you may use an Enum instance.

    class Color(Enum):\n    RED = \"red\"\n    GREEN = \"green\"\n    BLUE = \"blue\"\n\nm.choice(Color.BLUE)\n

    Alternatively, you may use an Enum instances list.

    m.choice([Color.GREEN, Color.BLUE])\n

    required title str

    Form title

    '' default str | TagValue | None

    The value of the checked choice.

    m.choice({\"one\": 1, \"two\": 2}, default=2)  # returns 2\n

    None skippable bool

    If there is a single option, choose it directly, without a dialog.

    True launch bool

    If the chosen value is a callback, we directly call it and return its return value.

    True

    Returns:

    Type Description TagValue | list[TagValue] | Any

    The chosen value.

    TagValue | list[TagValue] | Any

    If launch=True and the chosen value is a callback, we call it and return its result.

    Info

    To tackle a more detailed form, see Tag.choices.

    "},{"location":"Mininterface/#mininterface.Mininterface.form","title":"form(form=None, title='', *, submit=True)","text":"

    Prompt the user to fill up an arbitrary form.

    Use scalars, enums, enum instances, objects like datetime, Paths or their list.

    from enum import Enum\nfrom mininterface import run, Tag\n\nclass Color(Enum):\n    RED = \"red\"\n    GREEN = \"green\"\n    BLUE = \"blue\"\n\nm = run()\nout = m.form({\n    \"my_number\": 1,\n    \"my_boolean\": True,\n    \"my_enum\": Color,\n    \"my_tagged\": Tag(\"\", name='Tagged value', description='Long hint'),\n    \"my_path\": Path(\"/tmp\"),\n    \"my_paths\": [Path(\"/tmp\")],\n    \"My enum with default\": Color.BLUE\n})\n

    Parameters:

    Name Type Description Default form DataClass | Type[DataClass] | FormDict | None

    We accept a dataclass type, a dataclass instance, a dict or None.

    • If dict, we expect a dict of {labels: value}. The form widget infers from the default value type. The dict can be nested, it can contain a subgroup. The value might be a Tag that allows you to add descriptions.

    A checkbox example: {\"my label\": Tag(True, \"my description\")}

    • If None, the self.env is being used as a form, allowing the user to edit whole configuration. (Previously fetched from CLI and config file.)
    None title str

    Optional form title

    '' submit str | bool

    Set the submit button text (by default 'Ok') or hide it with False.

    True

    Returns:

    Name Type Description dataclass FormDict | DataClass | EnvClass

    If the form is null, the output is self.env.

    dataclass FormDict | DataClass | EnvClass

    If the form is a dataclass type or a dataclass instance, the output is the dataclass instance.

    dict FormDict | DataClass | EnvClass

    If the form is a dict, the output is another dict.

    Whereas the original dict stays intact (with the values updated), we return a new raw dict with all values resolved (all Tag objects are resolved to their value).

    original = {\"my label\": Tag(True, \"my description\")}\noutput = m.form(original)  # Sets the label to False in the dialog\n\n# Original dict was updated\nprint(original[\"my label\"])  # Tag(False, \"my description\")\n\n# Output dict is resolved, contains only raw values\nprint(output[\"my label\"])  # False\n

    Why this behaviour? You need to do some validation, hence you put Tag objects in the input dict. Then, you just need to work with the values.

    original = {\"my label\": Tag(True, \"my description\")}\noutput = m.form(original)  # Sets the label to False in the dialog\noutput[\"my_label\"]\n

    In the case you are willing to re-use the dict, you need not to lose the definitions, hence you end up with accessing via the .val.

    original = {\"my label\": Tag(True, \"my description\")}\n\nfor i in range(10):\n    m.form(original, f\"Attempt {i}\")\n    print(\"The result\", original[\"my label\"].val)\n
    "},{"location":"Mininterface/#mininterface.Mininterface.is_yes","title":"is_yes(text)","text":"

    Display confirm box, focusing yes.

    m = run()\nprint(m.is_yes(\"Is that alright?\"))  # True/False\n

    "},{"location":"Mininterface/#mininterface.Mininterface.is_no","title":"is_no(text)","text":"

    Display confirm box, focusing no.

    "},{"location":"Overview/","title":"Overview","text":"

    Via the run function you get access to the CLI, possibly enriched from the config file. Then, you receive all data as m.env object and dialog methods in a proper UI.

    graph LR\n    subgraph mininterface\n        run --> GUI\n        run --> TUI\n        run --> env\n        CLI --> run\n        id1[config file] --> CLI\n    end\n    program --> run\n
    "},{"location":"Overview/#basic-usage","title":"Basic usage","text":"

    Use a common dataclass, a Pydantic BaseModel or an attrs model to store the configuration. Wrap it to the run function that returns an interface m. Access the configuration via m.env or use it to prompt the user m.is_yes(\"Is that alright?\").

    There are a lot of supported types you can use, not only scalars and well-known objects (Path, datetime), but also functions, iterables (like list[Path]) and union types (like int | None). To do even more advanced things, stick the value to a powerful Tag or its subclasses. Ex. for a validation only, use its Validation alias.

    At last, use Facet to tackle the interface from the back-end (m) or the front-end (Tag) side.

    "},{"location":"Overview/#ide-suggestions","title":"IDE suggestions","text":"

    The immediate benefit is the type suggestions you see in an IDE. Imagine following code:

    from dataclasses import dataclass\nfrom mininterface import run\n\n@dataclass\nclass Env:\n    my_paths: list[Path]\n    \"\"\" The user is forced to input Paths. \"\"\"\n\n\n@dataclass\nclass Dialog:\n    my_number: int = 2\n    \"\"\" A number \"\"\"\n

    Now, accessing the main env will trigger the hint.

    Calling the form with an empty parameter will trigger editing the main env

    Putting there a dict will return the dict too.

    Putting there a dataclass type causes it to be resolved.

    Should you have a resolved dataclass instance, put it there.

    As you see, its attributes are hinted alongside their description.

    Should the dataclass cannot be easily investigated by the IDE (i.e. a required field), just annotate the output.

    "},{"location":"Overview/#nested-configuration","title":"Nested configuration","text":"

    You can easily nest the configuration. (See also Tyro Hierarchical Configs).

    Just put another dataclass inside the config file:

    @dataclass\nclass FurtherConfig:\n    token: str\n    host: str = \"example.org\"\n\n@dataclass\nclass Env:\n    further: FurtherConfig\n\n...\nm = run(Env)\nprint(m.env.further.host)  # example.org\n

    The attributes can by defaulted by CLI:

    $./program.py --further.host example.net\n

    And in a YAML config file. Note that you are not obliged to define all the attributes, a subset will do. (Ex. you do not need to specify token too.)

    further:\n  host: example.com\n
    "},{"location":"Overview/#all-possible-interfaces","title":"All possible interfaces","text":"

    Normally, you get an interface through mininterface.run but if you do not wish to parse CLI and config file, you can invoke one directly.

    Several interfaces exist:

    • Mininterface \u2013 The base interface the others are fully compatible with.
    • GuiInterface = TkInterface \u2013 A tkinter window.
    • TuiInterface \u2013 An interactive terminal.
    • TextualInterface \u2013 If textual installed, rich interface is used.
    • TextInterface \u2013 Plain text only interface with no dependency as a fallback.
    • ReplInterface \u2013 A debug terminal. Invokes a breakpoint after every dialog.

    How to invoke a specific interface.

    with TuiInterface(\"My program\") as m:\n    number = m.ask_number(\"Returns number\")\n
    "},{"location":"Standalone/","title":"Standalone","text":"

    When invoked directly, it creates simple GUI dialogs.

    $ mininterface  --help\nusage: Mininterface [-h] [OPTIONS]\n\nSimple GUI dialog. Outputs the value the user entered.\n\n\u256d\u2500 options \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256e\n\u2502 -h, --help              show this help message and exit                                   \u2502\n\u2502 --alert STR             Display the OK dialog with text. (default: '')                    \u2502\n\u2502 --ask STR               Prompt the user to input a text. (default: '')                    \u2502\n\u2502 --ask-number STR        Prompt the user to input a number. Empty input = 0. (default: '') \u2502\n\u2502 --is-yes STR            Display confirm box, focusing yes. (default: '')                  \u2502\n\u2502 --is-no STR             Display confirm box, focusing no. (default: '')                   \u2502\n\u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256f\n

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

    $ mininterface  --ask-number \"What's your age?\"  # GUI or TUI window invoked\n18\n

    "},{"location":"Tag/","title":"Tag","text":"

    Wrapper around a value that encapsulates a description, validation etc. When you provide a value to an interface, you may instead use this object.

    Bridge between the input values and a UI widget. The widget is created with the help of this object, then transforms the value back (str to int conversion etc).

    "},{"location":"Tag/#mininterface.Tag.val","title":"val: TagValue = None","text":"

    The value wrapped by Tag. It can be any value.

    from mininterface import run, Tag\n\ntag = Tag(True, \"This is my boolean\", bool)\nm = run()\nm.form({\"My boolean\": tag})\nprint(tag.val)  # True/False\nprint()\n

    The encapsulated value is True, tag.description is 'This is my boolean', tag.annotation is bool and 'My boolean' is used as tag.name.

    Tip

    If the Tag is nested, the info is fetched to the outer Tag. When updated, the inner Tag value updates accordingly.

    tag = Tag(Tag(True))\n
    "},{"location":"Tag/#mininterface.Tag.description","title":"description: str = ''","text":"

    The description displayed in the UI.

    "},{"location":"Tag/#mininterface.Tag.annotation","title":"annotation: type | None = None","text":"

    Used for validation (ex. to convert an empty string to None). If not set, will be determined automatically from the val type.

    "},{"location":"Tag/#mininterface.Tag.name","title":"name: str | None = None","text":"

    Name displayed in the UI.

    "},{"location":"Tag/#mininterface.Tag.validation","title":"validation: Callable[[Tag], ValidationResult | tuple[ValidationResult, TagValue]] | None = None","text":"

    When the user submits the form, the values are validated (and possibly transformed) with a callback function. If the validation fails, user is prompted to edit the value. Return True if validation succeeded or False or an error message when it failed.

    ValidationResult is a bool or the error message (that implicitly means it has failed).

    def check(tag: Tag):\n    if tag.val < 10:\n        return \"The value must be at least 10\"\nm.form({\"number\", Tag(12, validation=check)})\n

    Either use a custom callback function or mininterface.validators.

    from mininterface.validators import not_empty\nm.form({\"number\", Tag(\"\", validation=not_empty)})\n# User cannot leave the field empty.\n

    You may use the validation in a type annotation.

    from mininterface import Tag, Validation\n@dataclass\nclass Env:\n    my_text: Annotated[str, Validation(not_empty) = \"will not be emtpy\"\n\n    # which is an alias for:\n    # my_text: Annotated[str, Tag(validation=not_empty)] = \"will not be emtpy\"\n

    NOTE Undocumented feature, we can return tuple [ValidationResult, FieldValue] to set the self.val.

    "},{"location":"Tag/#mininterface.Tag.choices","title":"choices: ChoicesType | None = None","text":"

    Print the radio buttons / select box. Constraint the value.

    from dataclasses import dataclass\nfrom typing import Annotated\nfrom mininterface import run, Choices\n\n@dataclass\nclass Env:\n    foo: Annotated[\"str\", Choices(\"one\", \"two\")] = \"one\"\n    # `Choices` is an alias for `Tag(choices=)`\n\nm = run(Env)\nm.form()  # prompts a dialog\n

    Info

    When dealing with a simple use case, use the mininterface.choice dialog.

    "},{"location":"Tag/#mininterface.Tag.on_change","title":"on_change: Callable[[Tag], Any] | None = None","text":"

    Accepts a callback that launches whenever the value changes (if the validation succeeds). The callback runs while the dialog is still running. The return value of the callback is currently not used.

    In the following example, we alter the heading title according to the chosen value.

    from mininterface import run, Tag\n\ndef callback(tag: Tag):\n    tag.facet.set_title(f\"Value changed to {tag.val}\")\n\nm = run()\nm.facet.set_title(\"Click the checkbox\")\nm.form({\n    \"My choice\": Tag(choices=[\"one\", \"two\"], on_change=callback)\n})\n

    "},{"location":"Tag/#mininterface.Tag.facet","title":"facet: Optional[Facet] = None","text":"

    Access to the UI facet from the front-end side. (Read Mininterface.facet to access from the back-end side.)

    Set the UI facet from within a callback, ex. a validator.

    from mininterface import run, Tag\n\ndef my_check(tag: Tag):\n    tag.facet.set_title(\"My form title\")\n    return \"Validation failed\"\n\nwith run(title='My window title') as m:\n    m.form({\"My form\": Tag(1, validation=my_check)})\n

    This happens when you click ok.

    "},{"location":"Tag/#mininterface.Tag.original_val","title":"original_val = None","text":"

    Meant to be read only in callbacks. The original value, preceding UI change. Handy while validating.

    def check(tag.val):\n    if tag.val != tag.original_val:\n        return \"You have to change the value.\"\nm.form({\"number\", Tag(8, validation=check)})\n
    "},{"location":"Tag/#mininterface.Tag.set_val","title":"set_val","text":"

    Sets the value without any checks.

    "},{"location":"Tag/#mininterface.Tag.update","title":"update","text":"

    UI value \u2192 Tag value \u2192 original value. (With type conversion and checks.)

    Parameters:

    Name Type Description Default ui_value TagValue

    The value as it has been updated in a UI. Update accordingly the value in the original linked dict/object the mininterface was invoked with.

    Validates the type and do the transformation. (Ex: Some values might be nulled from \"\".)

    required

    Returns:

    Type Description bool

    bool, whether the value is alright or whether the revision is needed.

    "},{"location":"Tag/#mininterface.Choices","title":"Choices(*choices)","text":"

    An alias, see Tag.choices

    "},{"location":"Tag/#helper-types","title":"Helper types","text":""},{"location":"Tag/#mininterface.tag.ValidationResult","title":"ValidationResult = bool | ErrorMessage","text":"

    Callback validation result is either boolean or an error message.

    "},{"location":"Tag/#mininterface.tag.ErrorMessage","title":"ErrorMessage = TypeVar('ErrorMessage')","text":"

    A string, callback validation error message.

    "},{"location":"Tag/#mininterface.tag.TagValue","title":"TagValue = TypeVar('TagValue')","text":"

    Any value.

    "},{"location":"Tag/#mininterface.tag.ChoicesType","title":"ChoicesType = list[TagValue] | tuple[TagValue] | set[TagValue] | dict[ChoiceLabel, TagValue] | list[Enum] | Type[Enum]","text":"

    You can denote the choices in many ways. Either put options in an iterable or to a dict {labels: value}. Values might be Tags as well.

    See mininterface.choice or Tag.choices for examples.

    "},{"location":"Types/","title":"Types","text":"

    Various types are supported:

    • scalars
    • functions
    • well-known objects (Path, datetime)
    • iterables (like list[Path])
    • custom classes (somewhat)
    • union types (like int | None)

    Take a look how it works with the variables organized in a dataclass:

    from dataclasses import dataclass\nfrom pathlib import Path\n\nfrom mininterface import run\n\n\n@dataclass\nclass Env:\n    my_number: int = 1\n    \"\"\" A dummy number \"\"\"\n    my_boolean: bool = True\n    \"\"\" A dummy boolean \"\"\"\n    my_conditional_number: int | None = None\n    \"\"\" A number that can be null if left empty \"\"\"\n    my_path: Path = Path(\"/tmp\")\n    \"\"\" A dummy path \"\"\"\n\n\nm = run(Env)  # m.env contains an Env instance\nm.form()  # Prompt a dialog; m.form() without parameter edits m.env\nprint(m.env)\n# Env(my_number=1, my_boolean=True, my_path=PosixPath('/tmp'),\n#  my_point=<__main__.Point object at 0x7ecb5427fdd0>)\n

    Variables organized in a dict:

    Along scalar types, there is (basic) support for common iterables or custom classes.

    from mininterface import run\n\nclass Point:\n    def __init__(self, i: int):\n        self.i = i\n\n    def __str__(self):\n        return str(self.i)\n\n\nvalues = {\"my_number\": 1,\n          \"my_list\": [1, 2, 3],\n          \"my_point\": Point(10)\n          }\n\nm = run()\nm.form(values)  # Prompt a dialog\nprint(values)  # {'my_number': 2, 'my_list': [2, 3], 'my_point': <__main__.Point object...>}\nprint(values[\"my_point\"].i)  # 100\n

    "},{"location":"Types/#mininterface.types.CallbackTag","title":"CallbackTag","text":"

    Callback function is guaranteed to receives the Tag as a parameter.

    Warning

    Experimental. May change.

    For the following examples, we will use these custom callback functions:

    from mininterface import run\n\ndef callback_raw():\n    \"\"\" Dummy function \"\"\"\n    print(\"Priting text\")\n    return 50\n\ndef callback_tag(tag: Tag):\n    \"\"\" Receives a tag \"\"\"\n    print(\"Printing\", type(tag))\n    return 100\n\nm = run()\n

    Use as buttons in a form:

    m.form({\"Button\": callback_raw})\nm.form({\"Button\": CallbackTag(callback_tag)})\n

    Via form, we receive the function handler:

    out = m.form({\n    \"My choice\": Tag(choices=[callback_raw, CallbackTag(callback_tag)])\n})\nprint(out)  # {'My choice': <function callback_raw at 0x7ae5b3e74ea0>}\n

    Via choice, we receive the function output:

    out = m.choice({\n    \"My choice1\": callback_raw,\n    \"My choice2\": CallbackTag(callback_tag),\n    # Not supported: \"My choice3\": Tag(callback_tag, annotation=CallbackTag),\n})\nprint(out)  # output of callback0 or callback_tag, ex:\n#    Printing <class 'mininterface.types.CallbackTag'>\n#    100\n

    You may use callback in a dataclass.

    @dataclass\nclass Callbacks:\n    p1: Callable = callback0\n    p2: Annotated[Callable, CallbackTag(description=\"Foo\")] = callback_tag\n    # Not supported: p3: CallbackTag = callback_tag\n    # Not supported: p4: CallbackTag = field(default_factory=CallbackTag(callback_tag))\n    # Not supported: p5: Annotated[Callable, Tag(description=\"Bar\", annotation=CallbackTag)] = callback_tag\n\nm = run(Callbacks)\nm.form()\n

    "},{"location":"Types/#mininterface.types.PathTag","title":"PathTag","text":"

    Use this helper object to select files.

    In the following example, we see that it is not always needed to use this object.

    • File 1 \u2013 plain detection, button to a file picker appeared.
    • File 2 \u2013 the same.
    • File 3 \u2013 we specified multiple paths can be selected.
    from pathlib import Path\nfrom mininterface import run, Tag\nfrom mininterface.aliases import PathTag\n\nm = run()\nout = m.form({\n    \"File 1\": Path(\"/tmp\"),\n    \"File 2\": Tag(\"\", annotation=Path),\n    \"File 3\": PathTag([Path(\"/tmp\")], multiple=True),\n})\nprint(out)\n# {'File 1': PosixPath('/tmp'), 'File 2': PosixPath('.'), 'File 3': [PosixPath('/tmp')]}\n

    "},{"location":"Types/#mininterface.types.PathTag.multiple","title":"multiple: str = False","text":"

    The user can select multiple files.

    "},{"location":"Types/#mininterface.types.Validation","title":"Validation(check)","text":"

    Alias to Tag(validation=...)

    from mininterface import Tag, Validation\n@dataclass\nclass Env:\n    my_text: Annotated[str, Validation(not_empty) = \"will not be emtpy\"\n\n    # which is an alias for:\n    # my_text: Annotated[str, Tag(validation=not_empty)] = \"will not be emtpy\"\n

    Parameters:

    Name Type Description Default check Callable[[Tag], ValidationResult | tuple[ValidationResult, TagValue]]

    Callback function.

    required"},{"location":"Types/#mininterface.types.Choices","title":"Choices(*choices)","text":"

    An alias, see Tag.choices

    "},{"location":"Validation/","title":"Validation","text":"

    We recommend to use the dataclass and validate with the Annotated keyword. We use a Validation type here.

    from typing import Annotated\nfrom mininterface.validators import not_empty\nfrom mininterface import Validation\n\n@dataclass\nclass Env:\n    test: Annotated[str, Validation(not_empty)] = \"hello\"\n

    Under the hood, this is just a Tag.

    @dataclass\nclass Env:\n    test: Annotated[str, Tag(validation=not_empty)] = \"hello\"\n

    Why we used it in an Annotated statement? To preserve the date type.

    @dataclass\nclass Env:\n    my_string: Tag = Tag(\"hello\", validation=not_empty)\n\nm = run(Env)\nprint(type(m.env.my_string))  # Tag\nprint(m.env.my_string.val)  # hello\n
    "},{"location":"Validation/#validators","title":"validators","text":"

    Functions suitable for Tag validation. When the user submits a value whose validation fails, they are prompted to edit the value.

    m = run()\nmy_dict = m.form({\"my_text\", Tag(\"\", validation=validators.not_empty)})\nmy_dict[\"my_text\"]  # You can be sure the value is not empty here.\n

    Note that alternatively to this module, you may validate with Pydantic or an attrs model.

    from pydantic import BaseModel, Field\n\nclass MyModel(BaseModel):\n    restrained: str = Field(default=\"hello\", max_length=5)\n
    import attr\nfrom attr.validators import max_len\n\n@attr.s\nclass AttrsModel:\n    restrained: str = attr.ib(default=\"hello\", validator=max_len(5))\n
    "},{"location":"Validation/#mininterface.validators.not_empty","title":"not_empty(tag)","text":"

    Assures that Tag the user has written a value and did not let the field empty.

    from mininterface import Tag, validators, run\n\nm = run()\nm.form({\"my_text\": Tag(\"\", validation=validators.not_empty)})\n# User cannot leave the string field empty.\n

    When submitting an empty value, a warning appears:

    Note that for Path, an empty string is converted to an empty Path('.'), hence '.' too is considered as an empty input and the user is not able to set '.' as a value. This does not seem to me as a bad behaviour as in CLI you clearly see the CWD, whereas in a UI the CWD is not evident.

    Parameters:

    Name Type Description Default tag Tag required"},{"location":"Validation/#mininterface.validators.limit","title":"limit(maxOrMin=None, max_=None, lt=None, gt=None, transform=False)","text":"

    Limit a number range or a string length.

    Either use as limit(maximum) or limit(minimum, maximum).

    Parameters:

    Name Type Description Default maximum int

    limit(maximum) \u2013 from zero (including) to maximum (including)

    required minimum int

    limit(minimum, maximum) \u2013 From minimum (including) to maximum (including)

    required lt float | None

    lesser than

    None gt float | None

    greater than

    None transform bool

    If the value is not withing the limit, transform it to a boundary.

    from mininterface import run, Tag\nfrom mininterface.validators import limit\n\nm = run()\nm.form({\"my_number\": Tag(2, validation=limit(1, 10, transform=True))})\n# Put there '50' \u2192 transformed to 10 and dialog reappears\n# with 'Value must be between 1 and 10.'\n

    False"},{"location":"run/","title":"Run","text":"

    The main access, start here. Wrap your configuration dataclass into run to access the interface. An interface is chosen automatically, with the preference of the graphical one, regressed to a text interface for machines 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.

    Parameters:

    Name Type Description Default env_class Type[EnvClass] | None

    Dataclass with the configuration. Their values will be modified with the CLI arguments.

    None ask_on_empty_cli bool

    If program was launched with no arguments (empty CLI), invokes self.form() to edit the fields. (Withdrawn when ask_for_missing happens.)

    @dataclass\nclass Env:\nnumber: int = 3\ntext: str = \"\"\nm = run(Env, ask_on_empty=True)\n

    $ program.py  #  omitting all parameters\n# Dialog for `number` and `text` appears\n$ program.py --number 3\n# No dialog appears\n
    False title str

    The main title. If not set, taken from prog or program name.

    '' config_file Path | str | bool

    File to load YAML to be merged with the configuration. You do not have to re-define all the settings in the config file, you can choose a few. If set to True (default), we try to find one in the current working dir, whose name stem is the same as the program's. Ex: program.py will search for program.yaml. If False, no config file is used.

    True add_verbosity bool

    Adds the verbose flag that automatically sets the level to logging.INFO (-v) or logging.DEBUG (-vv).

    import logging\nlogger = logging.getLogger(__name__)\n\nm = run(Env, add_verbosity=True)\nlogger.info(\"Info shown\") # needs `-v` or `--verbose`\nlogger.debug(\"Debug not shown\")  # needs `-vv`\n# $ program.py --verbose\n# Info shown\n
    $ program.py --verbose\nInfo shown\n
    True ask_for_missing bool

    If some required fields are missing at startup, we ask for them in a UI instead of program exit.

    @dataclass\nclass Env:\n    required_number: int\nm = run(Env, ask_for_missing=True)\n
    $ program.py  # omitting --required-number\n# Dialog for `required_number` appears\n
    True interface Type[Mininterface]

    Which interface to prefer. By default, we use the GUI, the fallback is the TUI. See the full list of possible interfaces.

    GuiInterface or TuiInterface Kwargs

    The same as for argparse.ArgumentParser.

    Returns:

    Type Description Mininterface[EnvClass]

    An interface, ready to be used.

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

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

    "}]} \ No newline at end of file +{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"","title":"Mininterface \u2013 access to GUI, TUI, CLI and config files","text":"

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

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

    from dataclasses import dataclass\nfrom mininterface import run\n\n@dataclass\nclass Env:\n    \"\"\" This calculates something. \"\"\"\n\n    my_flag: bool = False\n    \"\"\" This switches the functionality \"\"\"\n\n    my_number: int = 4\n    \"\"\" This number is very important \"\"\"\n\nif __name__ == \"__main__\":\n    m = run(Env, prog=\"My application\")\n    m.form()\n    # Attributes are suggested by the IDE\n    # along with the hint text 'This number is very important'.\n    print(m.env.my_number)\n
    "},{"location":"#contents","title":"Contents","text":"
    • You got CLI
    • You got config file management
    • You got dialogues
    • Background
    • Installation
    • Docs
    • Examples
    "},{"location":"#you-got-cli","title":"You got CLI","text":"

    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.

    $ ./hello.py    --help\nusage: My application [-h] [-v] [--my-flag | --no-my-flag] [--my-number INT]\n\nThis calculates something.\n\n\u256d\u2500 options \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256e\n\u2502 -h, --help             show this help message and exit                  \u2502\n\u2502 -v, --verbose          Verbosity level. Can be used twice to increase.  \u2502\n\u2502 --my-flag, --no-my-flag                                                 \u2502\n\u2502                        This switches the functionality (default: False) \u2502\n\u2502 --my-number INT        This number is very important (default: 4)       \u2502\n\u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256f\n
    "},{"location":"#you-got-config-file-management","title":"You got config file management","text":"

    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.

    my_number: 555\n
    "},{"location":"#you-got-dialogues","title":"You got dialogues","text":"

    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.

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

    "},{"location":"#background","title":"Background","text":"

    Wrapper between the tyro argparse replacement and 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 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 --my-flag causes env.my_flag 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.

    "},{"location":"#installation","title":"Installation","text":"

    Install with a single command from PyPi.

    pip install mininterface\n
    "},{"location":"#minimal-installation","title":"Minimal installation","text":"

    Should you need just the CLI part and you are happy with basic text dialogs, use these commands instead:

    pip install --no-dependencies mininterface\npip install tyro typing_extensions pyyaml\n
    "},{"location":"#docs","title":"Docs","text":"

    See the docs overview at https://cz-nic.github.io/mininterface/.

    "},{"location":"#examples","title":"Examples","text":"

    A powerful m.form dialog method accepts either a dataclass or a dict. Take a look on both.

    "},{"location":"#a-complex-dataclass","title":"A complex dataclass.","text":"
    from typing import Annotated\nfrom dataclasses import dataclass\nfrom mininterface.validators import not_empty\nfrom mininterface import run, Tag, Validation\n\n@dataclass\nclass NestedEnv:\n  another_number: int = 7\n  \"\"\" This field is nested \"\"\"\n\n@dataclass\nclass Env:\n  nested_config: NestedEnv\n\n  mandatory_str: str\n  \"\"\" As there is no default value, you will be prompted automatically to fill up the field \"\"\"\n\n  my_number: int | None = None\n  \"\"\" This is not just a dummy number, if left empty, it is None. \"\"\"\n\n  my_string: str = \"Hello\"\n  \"\"\" A dummy string \"\"\"\n\n  my_flag: bool = False\n  \"\"\" Checkbox test \"\"\"\n\n  my_validated: Annotated[str, Validation(not_empty)] = \"hello\"\n  \"\"\" A validated field \"\"\"\n\nm = run(Env, title=\"My program\")\n# See some values\nprint(m.env.nested_config.another_number)  # 7\nprint(m.env)\n# Env(nested_config=NestedEnv(another_number=7), my_number=5, my_string='Hello', my_flag=False, my_validated='hello')\n\n# Edit values in a dialog\nm.form()\n

    As the attribute mandatory_str requires a value, a prompt appears automatically:

    Then, full form appears:

    "},{"location":"#form-with-paths","title":"Form with paths","text":"

    We have a dict with some paths. Here is how it looks.

    from pathlib import Path\nfrom mininterface import run, Tag\n\nm = run(title=\"My program\")\nmy_dictionary = {\n  \"paths\": Tag(\"\", annotation=list[Path]),\n  \"default_paths\": Tag([Path(\"/tmp\"), Path(\"/usr\")], annotation=list[Path])\n  }\n\n# Edit values in a dialog\nm.form(my_dictionary)\n

    "},{"location":"Changelog/","title":"Changelog","text":""},{"location":"Changelog/#070","title":"0.7.0","text":"
    • hidden --integrate-to-system argument
    • interfaces migrated to mininterface.interfaces to save around 50 ms starting time due to lazy loading
    • SubcommandPlaceholder
    "},{"location":"Exceptions/","title":"Exceptions","text":"

    Exceptions that might make sense to be used outside the library.

    "},{"location":"Exceptions/#mininterface.exceptions.Cancelled","title":"Cancelled","text":"

    User has cancelled. A SystemExit based exception noting that the program exits without a traceback, ex. if user hits the escape or closes the window.

    "},{"location":"Exceptions/#mininterface.exceptions.ValidationFail","title":"ValidationFail","text":"

    Signal to the form that submit failed and we want to restore it.

    "},{"location":"Exceptions/#mininterface.exceptions.InterfaceNotAvailable","title":"InterfaceNotAvailable","text":"

    Interface failed to init, ex. display not available in GUI. Or the underlying dependency was uninstalled.

    "},{"location":"Experimental/","title":"Experimental","text":""},{"location":"Experimental/#mininterface.experimental.SubmitButton","title":"SubmitButton","text":"

    Create a button. When clicked, the form submits. If submission succeeds (validation checks pass), its value becomes True.

    from pathlib import Path\nfrom mininterface import run, Tag\nfrom mininterface.experimental import SubmitButton\n\nm = run()\nout = m.form({\n    \"File name\": Tag(\"/tmp\", annotation=Path),\n    \"Append text\": {\n        \"My text\": \"\",\n        \"Append now\": SubmitButton()\n    },\n    \"Duplicate\": {\n        \"Method\": Tag(\"twice\", choices=[\"twice\", \"thrice\"]),\n        \"Duplicate now\": SubmitButton()\n    }\n})\n# Clicking on 'Append now' button\nprint(out)\n# {'File name': PosixPath('/tmp'),\n# 'Append text': {'My text': '', 'Append now': True},\n# 'Duplicate': {'Method': 'twice', 'Duplicate now': False}}\n

    "},{"location":"Facet/","title":"Facet","text":"

    A frontend side of the interface. While a dialog is open, this allows to set frontend properties like the heading.

    Read Tag.facet to see how to access from the front-end side. Read Mininterface.facet to see how to access from the back-end side.

    "},{"location":"Facet/#mininterface.facet.Facet.set_title","title":"set_title(text)","text":"

    Set the main heading.

    "},{"location":"Facet/#mininterface.facet.Facet.submit","title":"submit(_post_submit=None)","text":"

    Submits the whole form.

    ```python from mininterface import run, Tag

    def callback(tag: Tag): tag.facet.submit()

    m = run() out = m.form({ \"My choice\": Tag(choices=[\"one\", \"two\"], on_change=callback) })

    "},{"location":"Facet/#mininterface.facet.Facet.submit--continue-here-immediately-after-clicking-on-a-radio-button","title":"continue here immediately after clicking on a radio button","text":""},{"location":"Helper-types/","title":"Helper types","text":""},{"location":"Helper-types/#mininterface.tag.ValidationResult","title":"ValidationResult = bool | ErrorMessage","text":"

    Callback validation result is either boolean or an error message.

    "},{"location":"Helper-types/#mininterface.tag.ErrorMessage","title":"ErrorMessage = TypeVar('ErrorMessage')","text":"

    A string, callback validation error message.

    "},{"location":"Helper-types/#mininterface.tag.TagValue","title":"TagValue = TypeVar('TagValue', bound=Any)","text":"

    Any value. It is being wrapped by a Tag.

    "},{"location":"Helper-types/#mininterface.tag.ChoicesType","title":"ChoicesType = list[TagValue] | tuple[TagValue] | set[TagValue] | dict[ChoiceLabel, TagValue] | list[Enum] | Type[Enum]","text":"

    You can denote the choices in many ways. Either put options in an iterable or to a dict {labels: value}. Values might be Tags as well.

    See mininterface.choice or Tag.choices for examples.

    "},{"location":"Helper-types/#mininterface.form_dict.DataClass","title":"DataClass = TypeVar('DataClass')","text":"

    Any dataclass. Or a pydantic model or attrs.

    "},{"location":"Helper-types/#mininterface.form_dict.EnvClass","title":"EnvClass = TypeVar('EnvClass', bound=DataClass)","text":"

    Any dataclass. Its instance will be available through [miniterface.env] after CLI parsing.

    "},{"location":"Interfaces/","title":"Interfaces","text":""},{"location":"Interfaces/#all-possible-interfaces","title":"All possible interfaces","text":"

    Normally, you get an interface through mininterface.run but if you do not wish to parse CLI and config file, you can invoke one directly from mininterface.interfaces import *.

    Apart from the default Mininterface, the base interface the others are fully compatible with, several interfaces exist:

    How to invoke a specific interface.

    from mininterface.interfaces import TuiInterface\n\nwith TuiInterface(\"My program\") as m:\n    number = m.ask_number(\"Returns number\")\n
    "},{"location":"Interfaces/#guiinterface-tkinterface","title":"GuiInterface = TkInterface","text":"

    A tkinter window.

    "},{"location":"Interfaces/#tuiinterface","title":"TuiInterface","text":"

    An interactive terminal.

    "},{"location":"Interfaces/#textualinterface","title":"TextualInterface","text":"

    If textual installed, rich and mouse clickable interface is used.

    "},{"location":"Interfaces/#textinterface","title":"TextInterface","text":"

    Plain text only interface with no dependency as a fallback.

    "},{"location":"Interfaces/#replinterface","title":"ReplInterface","text":"

    A debug terminal. Invokes a breakpoint after every dialog.

    "},{"location":"Mininterface/","title":"Mininterface","text":"

    The base interface. You get one through mininterface.run which fills CLI arguments and config file to mininterface.env or you can create one directly (without benefiting from the CLI parsing).

    Raise

    Cancelled: A SystemExit based exception noting that the program exits without a traceback, ex. if user hits the escape.

    Raise

    InterfaceNotAvailable: Interface failed to init, ex. display not available in GUI.

    "},{"location":"Mininterface/#mininterface.Mininterface.env","title":"env: EnvClass = EnvInstance","text":"

    Parsed arguments, fetched from cli. Contains whole configuration (previously fetched from CLI and config file).

    $ program.py --number 10\n
    from dataclasses import dataclass\nfrom mininterface import run\n\n@dataclass\nclass Env:\n    number: int = 3\n    text: str = \"\"\n\nm = run(Env)\nprint(m.env.number)  # 10\n
    "},{"location":"Mininterface/#mininterface.Mininterface.facet","title":"facet = Facet(None, self.env)","text":"

    Access to the UI facet from the back-end side. (Read Tag.facet to access from the front-end side.)

    from mininterface import run\nwith run(title='My window title') as m:\n    m.facet.set_title(\"My form title\")\n    m.form({\"My form\": 1})\n

    "},{"location":"Mininterface/#mininterface.Mininterface.__enter__","title":"__enter__()","text":"

    When used in the with statement, the GUI window does not vanish between dialogs and it redirects the stdout to a text area.

    "},{"location":"Mininterface/#mininterface.Mininterface.alert","title":"alert(text)","text":"

    Prompt the user to confirm the text.

    "},{"location":"Mininterface/#mininterface.Mininterface.ask","title":"ask(text)","text":"

    Prompt the user to input a text.

    "},{"location":"Mininterface/#mininterface.Mininterface.ask_number","title":"ask_number(text)","text":"

    Prompt the user to input a number. Empty input = 0.

    m = run()  # receives a Mininterface object\nm.ask_number(\"What's your age?\")\n

    Parameters:

    Name Type Description Default text str

    The question text.

    required

    Returns:

    Type Description int

    Number

    "},{"location":"Mininterface/#mininterface.Mininterface.choice","title":"choice(choices, title='', _guesses=None, skippable=True, launch=True, _multiple=True, default=None)","text":"

    Prompt the user to select. Useful for a menu creation.

    Parameters:

    Name Type Description Default choices ChoicesType

    You can denote the choices in many ways. Either put options in an iterable:

    from mininterface import run\nm = run()\nm.choice([1, 2])\n

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

    m.choice({\"one\": 1, \"two\": 2})  # returns 1\n

    Alternatively, you may specify the names in Tags.

    m.choice([Tag(1, name=\"one\"), Tag(2, name=\"two\")])  # returns 1\n

    Alternatively, you may use an Enum.

    class Color(Enum):\n    RED = \"red\"\n    GREEN = \"green\"\n    BLUE = \"blue\"\n\nm.choice(Color)\n

    Alternatively, you may use an Enum instance.

    class Color(Enum):\n    RED = \"red\"\n    GREEN = \"green\"\n    BLUE = \"blue\"\n\nm.choice(Color.BLUE)\n

    Alternatively, you may use an Enum instances list.

    m.choice([Color.GREEN, Color.BLUE])\n

    required title str

    Form title

    '' default str | TagValue | None

    The value of the checked choice.

    m.choice({\"one\": 1, \"two\": 2}, default=2)  # returns 2\n

    None skippable bool

    If there is a single option, choose it directly, without a dialog.

    True launch bool

    If the chosen value is a callback, we directly call it and return its return value.

    True

    Returns:

    Type Description TagValue | list[TagValue] | Any

    The chosen value.

    TagValue | list[TagValue] | Any

    If launch=True and the chosen value is a callback, we call it and return its result.

    Info

    To tackle a more detailed form, see Tag.choices.

    "},{"location":"Mininterface/#mininterface.Mininterface.form","title":"form(form=None, title='', *, submit=True)","text":"

    Prompt the user to fill up an arbitrary form.

    Use scalars, enums, enum instances, objects like datetime, Paths or their list.

    from enum import Enum\nfrom mininterface import run, Tag\n\nclass Color(Enum):\n    RED = \"red\"\n    GREEN = \"green\"\n    BLUE = \"blue\"\n\nm = run()\nout = m.form({\n    \"my_number\": 1,\n    \"my_boolean\": True,\n    \"my_enum\": Color,\n    \"my_tagged\": Tag(\"\", name='Tagged value', description='Long hint'),\n    \"my_path\": Path(\"/tmp\"),\n    \"my_paths\": [Path(\"/tmp\")],\n    \"My enum with default\": Color.BLUE\n})\n

    Parameters:

    Name Type Description Default form DataClass | Type[DataClass] | FormDict | None

    We accept a dataclass type, a dataclass instance, a dict or None.

    • If dict, we expect a dict of {labels: value}. The form widget infers from the default value type. The dict can be nested, it can contain a subgroup. The value might be a Tag that allows you to add descriptions.

    A checkbox example: {\"my label\": Tag(True, \"my description\")}

    • If None, the self.env is being used as a form, allowing the user to edit whole configuration. (Previously fetched from CLI and config file.)
    None title str

    Optional form title

    '' submit str | bool

    Set the submit button text (by default 'Ok') or hide it with False.

    True

    Returns:

    Name Type Description dataclass FormDict | DataClass | EnvClass

    If the form is null, the output is self.env.

    dataclass FormDict | DataClass | EnvClass

    If the form is a dataclass type or a dataclass instance, the output is the dataclass instance.

    dict FormDict | DataClass | EnvClass

    If the form is a dict, the output is another dict.

    Whereas the original dict stays intact (with the values updated), we return a new raw dict with all values resolved (all Tag objects are resolved to their value).

    original = {\"my label\": Tag(True, \"my description\")}\noutput = m.form(original)  # Sets the label to False in the dialog\n\n# Original dict was updated\nprint(original[\"my label\"])  # Tag(False, \"my description\")\n\n# Output dict is resolved, contains only raw values\nprint(output[\"my label\"])  # False\n

    Why this behaviour? You need to do some validation, hence you put Tag objects in the input dict. Then, you just need to work with the values.

    original = {\"my label\": Tag(True, \"my description\")}\noutput = m.form(original)  # Sets the label to False in the dialog\noutput[\"my_label\"]\n

    In the case you are willing to re-use the dict, you need not to lose the definitions, hence you end up with accessing via the .val.

    original = {\"my label\": Tag(True, \"my description\")}\n\nfor i in range(10):\n    m.form(original, f\"Attempt {i}\")\n    print(\"The result\", original[\"my label\"].val)\n
    "},{"location":"Mininterface/#mininterface.Mininterface.is_yes","title":"is_yes(text)","text":"

    Display confirm box, focusing yes.

    m = run()\nprint(m.is_yes(\"Is that alright?\"))  # True/False\n

    "},{"location":"Mininterface/#mininterface.Mininterface.is_no","title":"is_no(text)","text":"

    Display confirm box, focusing no.

    "},{"location":"Overview/","title":"Overview","text":"

    Via the run function you get access to the CLI, possibly enriched from the config file. Then, you receive all data as m.env object and dialog methods in a proper UI.

    graph LR\n    subgraph mininterface\n        run --> GUI\n        run --> TUI\n        run --> env\n        CLI --> run\n        id1[config file] --> CLI\n    end\n    program --> run\n
    "},{"location":"Overview/#basic-usage","title":"Basic usage","text":"

    Use a common dataclass, a Pydantic BaseModel or an attrs model to store the configuration. Wrap it to the run function that returns an interface m. Access the configuration via m.env or use it to prompt the user m.is_yes(\"Is that alright?\").

    There are a lot of supported types you can use, not only scalars and well-known objects (Path, datetime), but also functions, iterables (like list[Path]) and union types (like int | None). To do even more advanced things, stick the value to a powerful Tag or its subclasses. Ex. for a validation only, use its Validation alias.

    At last, use Facet to tackle the interface from the back-end (m) or the front-end (Tag) side.

    "},{"location":"Overview/#ide-suggestions","title":"IDE suggestions","text":"

    The immediate benefit is the type suggestions you see in an IDE. Imagine following code:

    from dataclasses import dataclass\nfrom mininterface import run\n\n@dataclass\nclass Env:\n    my_paths: list[Path]\n    \"\"\" The user is forced to input Paths. \"\"\"\n\n\n@dataclass\nclass Dialog:\n    my_number: int = 2\n    \"\"\" A number \"\"\"\n

    Now, accessing the main env will trigger the hint.

    Calling the form with an empty parameter will trigger editing the main env

    Putting there a dict will return the dict too.

    Putting there a dataclass type causes it to be resolved.

    Should you have a resolved dataclass instance, put it there.

    As you see, its attributes are hinted alongside their description.

    Should the dataclass cannot be easily investigated by the IDE (i.e. a required field), just annotate the output.

    "},{"location":"Overview/#bash-completion","title":"Bash completion","text":"

    Run your program with a hidden --integrate-to-system flag and a tutorial will install bash completion.

    "},{"location":"Overview/#nested-configuration","title":"Nested configuration","text":"

    You can easily nest the configuration. (See also Tyro Hierarchical Configs).

    Just put another dataclass inside the config file:

    @dataclass\nclass FurtherConfig:\n    token: str\n    host: str = \"example.org\"\n\n@dataclass\nclass Env:\n    further: FurtherConfig\n\n...\nm = run(Env)\nprint(m.env.further.host)  # example.org\n

    The attributes can by defaulted by CLI:

    $./program.py --further.host example.net\n

    And in a YAML config file. Note that you are not obliged to define all the attributes, a subset will do. (Ex. you do not need to specify token too.)

    further:\n  host: example.com\n
    "},{"location":"Standalone/","title":"Standalone","text":"

    When invoked directly, it creates simple GUI dialogs.

    $ mininterface  --help\nusage: Mininterface [-h] [OPTIONS]\n\nSimple GUI dialog. Outputs the value the user entered.\n\n\u256d\u2500 options \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256e\n\u2502 -h, --help              show this help message and exit                                   \u2502\n\u2502 --alert STR             Display the OK dialog with text. (default: '')                    \u2502\n\u2502 --ask STR               Prompt the user to input a text. (default: '')                    \u2502\n\u2502 --ask-number STR        Prompt the user to input a number. Empty input = 0. (default: '') \u2502\n\u2502 --is-yes STR            Display confirm box, focusing yes. (default: '')                  \u2502\n\u2502 --is-no STR             Display confirm box, focusing no. (default: '')                   \u2502\n\u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256f\n

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

    $ mininterface  --ask-number \"What's your age?\"  # GUI or TUI window invoked\n18\n

    "},{"location":"Subcommands/","title":"Subcommands","text":"

    Dealing with CLI subcommands, from mininterface.subcommands import *

    "},{"location":"Subcommands/#mininterface.subcommands.Command","title":"Command","text":"

    The Command is automatically run while instantanied.

    Experimental \u2013 how should it receive _facet?

    Put list of Commands to the mininterface.run and divide your application into different sections. Alternative to argparse subcommands.

    Commands might inherit from the same parent to share the common attributes.

    "},{"location":"Subcommands/#mininterface.subcommands.Command--subcommandplaceholder","title":"SubcommandPlaceholder","text":"

    What if I need to use my program Special placeholder class SubcommandPlaceholder. This special class let the user to choose the subcommands via UI, while still benefiniting from default CLI arguments.

    "},{"location":"Subcommands/#mininterface.subcommands.Command--the-cli-behaviour","title":"The CLI behaviour:","text":"
    • ./program.py -> UI started with subcommand choice
    • ./program.py subcommand --flag -> special class SubcommandPlaceholder allows defining a common --flag while still starting UI with subcommand choice
    • ./program.py subcommand1 --flag -> program run
    • ./program.py subcommand1 -> fails to CLI for now
    "},{"location":"Subcommands/#mininterface.subcommands.Command--an-example-of-command-usage","title":"An example of Command usage","text":"
    from dataclasses import dataclass, field\nfrom pathlib import Path\nfrom mininterface import run\nfrom mininterface.exceptions import ValidationFail\nfrom mininterface.subcommands import Command, SubcommandPlaceholder\nfrom tyro.conf import Positional\n\n\n@dataclass\nclass SharedArgs(Command):\n    common: int\n    files: Positional[list[Path]] = field(default_factory=list)\n\n    def init(self):\n        self.internal = \"value\"\n\n\n@dataclass\nclass Subcommand1(SharedArgs):\n    my_local: int = 1\n\n    def run(self):\n        print(\"Common:\", self.common)  # user input\n        print(\"Number:\", self.my_local)  # 1 or user input\n        print(\"Internal:\", self.internal)\n        raise ValidationFail(\"The submit button blocked!\")\n\n\n@dataclass\nclass Subcommand2(SharedArgs):\n    def run(self):\n        self._facet.set_title(\"Button clicked\")  # you can access internal self._facet: Facet\n        print(\"Common files\", self.files)\n\n\nm = run([Subcommand1, Subcommand2, SubcommandPlaceholder])\nm.alert(\"App continue\")\n

    Let's start the program, passing there common flags, all HTML files in a folder and setting --common to 7.

    $ program.py  subcommand *.html  --common 7\n

    As you see, thanks to SubcommandPlaceholder, subcommand was not chosen yet. Click to the first button.

    and the terminal got:

    Common: 7\nNumber: 1\nInternal: value\n

    Click to the second button.

    Terminal output:

    Common files [PosixPath('page1.html'), PosixPath('page2.html')]\n

    "},{"location":"Subcommands/#mininterface.subcommands.Command--powerful-automation","title":"Powerful automation","text":"

    Note we use from tyro.conf import Positional to denote the positional argument. We did not have to write --files to put there HTML files.

    "},{"location":"Subcommands/#mininterface.subcommands.Command.init","title":"init()","text":"

    Just before the form appears. As the __post_init__ method is not guaranteed to run just once (internal CLI behaviour), you are welcome to override this method instead. You can use self._facet from within.

    "},{"location":"Subcommands/#mininterface.subcommands.Command.run","title":"run()","text":"

    This method is run automatically in CLI or by a button button it generates in a UI.

    "},{"location":"Subcommands/#mininterface.subcommands.SubcommandPlaceholder","title":"SubcommandPlaceholder","text":"

    Use this placeholder to choose the subcomannd via a UI.

    "},{"location":"Tag/","title":"Tag","text":"

    Wrapper around a value that encapsulates a description, validation etc. When you provide a value to an interface, you may instead use this object.

    Bridge between the input values and a UI widget. The widget is created with the help of this object, then transforms the value back (str to int conversion etc).

    "},{"location":"Tag/#mininterface.Tag.val","title":"val: TagValue = None","text":"

    The value wrapped by Tag. It can be any value.

    from mininterface import run, Tag\n\ntag = Tag(True, \"This is my boolean\", bool)\nm = run()\nm.form({\"My boolean\": tag})\nprint(tag.val)  # True/False\nprint()\n

    The encapsulated value is True, tag.description is 'This is my boolean', tag.annotation is bool and 'My boolean' is used as tag.name.

    Tip

    If the Tag is nested, the info is fetched to the outer Tag. When updated, the inner Tag value updates accordingly.

    tag = Tag(Tag(True))\n
    "},{"location":"Tag/#mininterface.Tag.description","title":"description: str = ''","text":"

    The description displayed in the UI.

    "},{"location":"Tag/#mininterface.Tag.annotation","title":"annotation: type | None = None","text":"

    Used for validation (ex. to convert an empty string to None). If not set, will be determined automatically from the val type.

    "},{"location":"Tag/#mininterface.Tag.name","title":"name: str | None = None","text":"

    Name displayed in the UI.

    "},{"location":"Tag/#mininterface.Tag.validation","title":"validation: Callable[[Tag], ValidationResult | tuple[ValidationResult, TagValue]] | None = None","text":"

    When the user submits the form, the values are validated (and possibly transformed) with a callback function. If the validation fails, user is prompted to edit the value. Return True if validation succeeded or False or an error message when it failed.

    ValidationResult is a bool or the error message (that implicitly means it has failed).

    def check(tag: Tag):\n    if tag.val < 10:\n        return \"The value must be at least 10\"\nm.form({\"number\", Tag(12, validation=check)})\n

    Either use a custom callback function or mininterface.validators.

    from mininterface.validators import not_empty\nm.form({\"number\", Tag(\"\", validation=not_empty)})\n# User cannot leave the field empty.\n

    You may use the validation in a type annotation.

    from mininterface import Tag, Validation\n@dataclass\nclass Env:\n    my_text: Annotated[str, Validation(not_empty) = \"will not be emtpy\"\n\n    # which is an alias for:\n    # my_text: Annotated[str, Tag(validation=not_empty)] = \"will not be emtpy\"\n

    NOTE Undocumented feature, we can return tuple [ValidationResult, FieldValue] to set the self.val.

    "},{"location":"Tag/#mininterface.Tag.choices","title":"choices: ChoicesType | None = None","text":"

    Print the radio buttons / select box. Constraint the value.

    from dataclasses import dataclass\nfrom typing import Annotated\nfrom mininterface import run, Choices\n\n@dataclass\nclass Env:\n    foo: Annotated[\"str\", Choices(\"one\", \"two\")] = \"one\"\n    # `Choices` is an alias for `Tag(choices=)`\n\nm = run(Env)\nm.form()  # prompts a dialog\n

    Info

    When dealing with a simple use case, use the mininterface.choice dialog.

    "},{"location":"Tag/#mininterface.Tag.on_change","title":"on_change: Callable[[Tag], Any] | None = None","text":"

    Accepts a callback that launches whenever the value changes (if the validation succeeds). The callback runs while the dialog is still running. The return value of the callback is currently not used.

    In the following example, we alter the heading title according to the chosen value.

    from mininterface import run, Tag\n\ndef callback(tag: Tag):\n    tag.facet.set_title(f\"Value changed to {tag.val}\")\n\nm = run()\nm.facet.set_title(\"Click the checkbox\")\nm.form({\n    \"My choice\": Tag(choices=[\"one\", \"two\"], on_change=callback)\n})\n

    "},{"location":"Tag/#mininterface.Tag.facet","title":"facet: Optional[Facet] = None","text":"

    Access to the UI facet from the front-end side. (Read Mininterface.facet to access from the back-end side.)

    Set the UI facet from within a callback, ex. a validator.

    from mininterface import run, Tag\n\ndef my_check(tag: Tag):\n    tag.facet.set_title(\"My form title\")\n    return \"Validation failed\"\n\nwith run(title='My window title') as m:\n    m.form({\"My form\": Tag(1, validation=my_check)})\n

    This happens when you click ok.

    "},{"location":"Tag/#mininterface.Tag.original_val","title":"original_val = None","text":"

    Meant to be read only in callbacks. The original value, preceding UI change. Handy while validating.

    def check(tag.val):\n    if tag.val != tag.original_val:\n        return \"You have to change the value.\"\nm.form({\"number\", Tag(8, validation=check)})\n
    "},{"location":"Tag/#mininterface.Tag.set_val","title":"set_val","text":"

    Sets the value without any checks.

    "},{"location":"Tag/#mininterface.Tag.update","title":"update","text":"

    UI value \u2192 Tag value \u2192 original value. (With type conversion and checks.)

    Parameters:

    Name Type Description Default ui_value TagValue

    The value as it has been updated in a UI. Update accordingly the value in the original linked dict/object the mininterface was invoked with.

    Validates the type and do the transformation. (Ex: Some values might be nulled from \"\".)

    required

    Returns:

    Type Description bool

    bool, whether the value is alright or whether the revision is needed.

    "},{"location":"Tag/#mininterface.Choices","title":"Choices(*choices)","text":"

    An alias, see Tag.choices

    "},{"location":"Types/","title":"Types","text":"

    Various types are supported:

    • scalars
    • functions
    • well-known objects (Path, datetime)
    • iterables (like list[Path])
    • custom classes (somewhat)
    • union types (like int | None)

    Take a look how it works with the variables organized in a dataclass:

    from dataclasses import dataclass\nfrom pathlib import Path\n\nfrom mininterface import run\n\n\n@dataclass\nclass Env:\n    my_number: int = 1\n    \"\"\" A dummy number \"\"\"\n    my_boolean: bool = True\n    \"\"\" A dummy boolean \"\"\"\n    my_conditional_number: int | None = None\n    \"\"\" A number that can be null if left empty \"\"\"\n    my_path: Path = Path(\"/tmp\")\n    \"\"\" A dummy path \"\"\"\n\n\nm = run(Env)  # m.env contains an Env instance\nm.form()  # Prompt a dialog; m.form() without parameter edits m.env\nprint(m.env)\n# Env(my_number=1, my_boolean=True, my_path=PosixPath('/tmp'),\n#  my_point=<__main__.Point object at 0x7ecb5427fdd0>)\n

    Variables organized in a dict:

    Along scalar types, there is (basic) support for common iterables or custom classes.

    from mininterface import run\n\nclass Point:\n    def __init__(self, i: int):\n        self.i = i\n\n    def __str__(self):\n        return str(self.i)\n\n\nvalues = {\"my_number\": 1,\n          \"my_list\": [1, 2, 3],\n          \"my_point\": Point(10)\n          }\n\nm = run()\nm.form(values)  # Prompt a dialog\nprint(values)  # {'my_number': 2, 'my_list': [2, 3], 'my_point': <__main__.Point object...>}\nprint(values[\"my_point\"].i)  # 100\n

    "},{"location":"Types/#mininterface.types.CallbackTag","title":"CallbackTag","text":"

    Callback function is guaranteed to receives the Tag as a parameter.

    Warning

    Experimental. May change.

    For the following examples, we will use these custom callback functions:

    from mininterface import run\n\ndef callback_raw():\n    \"\"\" Dummy function \"\"\"\n    print(\"Priting text\")\n    return 50\n\ndef callback_tag(tag: Tag):\n    \"\"\" Receives a tag \"\"\"\n    print(\"Printing\", type(tag))\n    return 100\n\nm = run()\n

    Use as buttons in a form:

    m.form({\"Button\": callback_raw})\nm.form({\"Button\": CallbackTag(callback_tag)})\n

    Via form, we receive the function handler:

    out = m.form({\n    \"My choice\": Tag(choices=[callback_raw, CallbackTag(callback_tag)])\n})\nprint(out)  # {'My choice': <function callback_raw at 0x7ae5b3e74ea0>}\n

    Via choice, we receive the function output:

    out = m.choice({\n    \"My choice1\": callback_raw,\n    \"My choice2\": CallbackTag(callback_tag),\n    # Not supported: \"My choice3\": Tag(callback_tag, annotation=CallbackTag),\n})\nprint(out)  # output of callback0 or callback_tag, ex:\n#    Printing <class 'mininterface.types.CallbackTag'>\n#    100\n

    You may use callback in a dataclass.

    @dataclass\nclass Callbacks:\n    p1: Callable = callback0\n    p2: Annotated[Callable, CallbackTag(description=\"Foo\")] = callback_tag\n    # Not supported: p3: CallbackTag = callback_tag\n    # Not supported: p4: CallbackTag = field(default_factory=CallbackTag(callback_tag))\n    # Not supported: p5: Annotated[Callable, Tag(description=\"Bar\", annotation=CallbackTag)] = callback_tag\n\nm = run(Callbacks)\nm.form()\n

    "},{"location":"Types/#mininterface.types.PathTag","title":"PathTag","text":"

    Use this helper object to select files.

    In the following example, we see that it is not always needed to use this object.

    • File 1 \u2013 plain detection, button to a file picker appeared.
    • File 2 \u2013 the same.
    • File 3 \u2013 we specified multiple paths can be selected.
    from pathlib import Path\nfrom mininterface import run, Tag\nfrom mininterface.aliases import PathTag\n\nm = run()\nout = m.form({\n    \"File 1\": Path(\"/tmp\"),\n    \"File 2\": Tag(\"\", annotation=Path),\n    \"File 3\": PathTag([Path(\"/tmp\")], multiple=True),\n})\nprint(out)\n# {'File 1': PosixPath('/tmp'), 'File 2': PosixPath('.'), 'File 3': [PosixPath('/tmp')]}\n

    "},{"location":"Types/#mininterface.types.PathTag.multiple","title":"multiple: str = False","text":"

    The user can select multiple files.

    "},{"location":"Types/#mininterface.types.Validation","title":"Validation(check)","text":"

    Alias to Tag(validation=...)

    from mininterface import Tag, Validation\n@dataclass\nclass Env:\n    my_text: Annotated[str, Validation(not_empty) = \"will not be emtpy\"\n\n    # which is an alias for:\n    # my_text: Annotated[str, Tag(validation=not_empty)] = \"will not be emtpy\"\n

    Parameters:

    Name Type Description Default check Callable[[Tag], ValidationResult | tuple[ValidationResult, TagValue]]

    Callback function.

    required"},{"location":"Types/#mininterface.types.Choices","title":"Choices(*choices)","text":"

    An alias, see Tag.choices

    "},{"location":"Validation/","title":"Validation","text":"

    We recommend to use the dataclass and validate with the Annotated keyword. We use a Validation type here.

    from typing import Annotated\nfrom mininterface.validators import not_empty\nfrom mininterface import Validation\n\n@dataclass\nclass Env:\n    test: Annotated[str, Validation(not_empty)] = \"hello\"\n

    Under the hood, this is just a Tag.

    @dataclass\nclass Env:\n    test: Annotated[str, Tag(validation=not_empty)] = \"hello\"\n

    Why we used it in an Annotated statement? To preserve the date type.

    @dataclass\nclass Env:\n    my_string: Tag = Tag(\"hello\", validation=not_empty)\n\nm = run(Env)\nprint(type(m.env.my_string))  # Tag\nprint(m.env.my_string.val)  # hello\n
    "},{"location":"Validation/#validators","title":"validators","text":"

    Functions suitable for Tag validation. When the user submits a value whose validation fails, they are prompted to edit the value.

    m = run()\nmy_dict = m.form({\"my_text\", Tag(\"\", validation=validators.not_empty)})\nmy_dict[\"my_text\"]  # You can be sure the value is not empty here.\n

    Note that alternatively to this module, you may validate with Pydantic or an attrs model.

    from pydantic import BaseModel, Field\n\nclass MyModel(BaseModel):\n    restrained: str = Field(default=\"hello\", max_length=5)\n
    import attr\nfrom attr.validators import max_len\n\n@attr.s\nclass AttrsModel:\n    restrained: str = attr.ib(default=\"hello\", validator=max_len(5))\n
    "},{"location":"Validation/#mininterface.validators.not_empty","title":"not_empty(tag)","text":"

    Assures that Tag the user has written a value and did not let the field empty.

    from mininterface import Tag, validators, run\n\nm = run()\nm.form({\"my_text\": Tag(\"\", validation=validators.not_empty)})\n# User cannot leave the string field empty.\n

    When submitting an empty value, a warning appears:

    Note that for Path, an empty string is converted to an empty Path('.'), hence '.' too is considered as an empty input and the user is not able to set '.' as a value. This does not seem to me as a bad behaviour as in CLI you clearly see the CWD, whereas in a UI the CWD is not evident.

    Parameters:

    Name Type Description Default tag Tag required"},{"location":"Validation/#mininterface.validators.limit","title":"limit(maxOrMin=None, max_=None, lt=None, gt=None, transform=False)","text":"

    Limit a number range or a string length.

    Either use as limit(maximum) or limit(minimum, maximum).

    Parameters:

    Name Type Description Default maximum int

    limit(maximum) \u2013 from zero (including) to maximum (including)

    required minimum int

    limit(minimum, maximum) \u2013 From minimum (including) to maximum (including)

    required lt float | None

    lesser than

    None gt float | None

    greater than

    None transform bool

    If the value is not within the limit, transform it to a boundary.

    from mininterface import run, Tag\nfrom mininterface.validators import limit\n\nm = run()\nm.form({\"my_number\": Tag(2, validation=limit(1, 10, transform=True))})\n# Put there '50' \u2192 transformed to 10 and dialog reappears\n# with 'Value must be between 1 and 10.'\n

    False"},{"location":"run/","title":"Run","text":"

    The main access, start here. Wrap your configuration dataclass into run to access the interface. An interface is chosen automatically, with the preference of the graphical one, regressed to a text interface for machines 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.

    Parameters:

    Name Type Description Default env_or_list Type[EnvClass] | list[Type[Command]] | None
    • dataclass Dataclass with the configuration. Their values will be modified with the CLI arguments.
    • list of Commands let you create multiple commands within a single program, each with unique options.
    • None You need just the dialogs, no CLI/config file parsing.
    None ask_on_empty_cli bool

    If program was launched with no arguments (empty CLI), invokes self.form() to edit the fields. (Withdrawn when ask_for_missing happens.)

    @dataclass\nclass Env:\nnumber: int = 3\ntext: str = \"\"\nm = run(Env, ask_on_empty=True)\n

    $ program.py  #  omitting all parameters\n# Dialog for `number` and `text` appears\n$ program.py --number 3\n# No dialog appears\n
    False title str

    The main title. If not set, taken from prog or program name.

    '' config_file Path | str | bool

    File to load YAML to be merged with the configuration. You do not have to re-define all the settings in the config file, you can choose a few. If set to True (default), we try to find one in the current working dir, whose name stem is the same as the program's. Ex: program.py will search for program.yaml. If False, no config file is used.

    True add_verbosity bool

    Adds the verbose flag that automatically sets the level to logging.INFO (-v) or logging.DEBUG (-vv).

    import logging\nlogger = logging.getLogger(__name__)\n\nm = run(Env, add_verbosity=True)\nlogger.info(\"Info shown\") # needs `-v` or `--verbose`\nlogger.debug(\"Debug not shown\")  # needs `-vv`\n# $ program.py --verbose\n# Info shown\n
    $ program.py --verbose\nInfo shown\n
    True ask_for_missing bool

    If some required fields are missing at startup, we ask for them in a UI instead of program exit.

    @dataclass\nclass Env:\n    required_number: int\nm = run(Env, ask_for_missing=True)\n
    $ program.py  # omitting --required-number\n# Dialog for `required_number` appears\n
    True interface Type[Mininterface] | Literal['gui'] | Literal['tui']

    Which interface to prefer. By default, we use the GUI, the fallback is the TUI. You may write \"gui\" or \"tui\" literal or pass a specific Mininterface type, see the full list of possible interfaces.

    None args Optional[Sequence[str]]

    Parse arguments from a sequence instead of the command line.

    None

    Kwargs: The same as for argparse.ArgumentParser.

    Returns:

    Type Description Mininterface[EnvClass]

    An interface, ready to be used.

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

    from dataclasses import dataclass\nfrom mininterface import run\n\n@dataclass\nclass Env:\n    my_number: int = 4\n\nwith run(Env) as m:\n    print(f\"Your important number is {m.env.my_number}\")\n    boolean = m.is_yes(\"Is that alright?\")\n

    "}]} \ No newline at end of file diff --git a/sitemap.xml.gz b/sitemap.xml.gz index 9ed08e1..a72535d 100644 Binary files a/sitemap.xml.gz and b/sitemap.xml.gz differ