Interacting with user¶
Everything about
yuio.ioand Yuio’s interactive capabilities.
Printing messages¶
Yuio offers a logging-like functions to print messages:
import yuio.app
import yuio.io
@yuio.app.app
def main():
yuio.io.heading("Message colors")
yuio.io.success("Success message is bold green")
yuio.io.failure("Failure message is bold red")
yuio.io.info("Info message is default color")
yuio.io.warning("Warning message is yellow")
yuio.io.error("Error message is red")
yuio.io.info("Messages can have `%r`", ["formatted", "content"])
if __name__ == "__main__":
main.run()
import yuio.app
import yuio.io
@yuio.app.app
def main():
yuio.io.heading("Message colors")
yuio.io.success("Success message is bold green")
yuio.io.failure("Failure message is bold red")
yuio.io.info("Info message is default color")
yuio.io.warning("Warning message is yellow")
yuio.io.error("Error message is red")
yuio.io.info(t"Messages can have `{['formatted', 'content']!r}`")
if __name__ == "__main__":
main.run()
These functions accept format strings that can have color tags and backticks:
yuio.io.info(
"Color tags are similar to XML: "
"<c bold green>this text is bold and green</c>" # [1]_
)
yuio.io.info(
"Backticks work like in Markdown: "
"`this is an escaped code, tags like <c red> don't work here.`"
)
yuio.io.info(
"You can escape backticks and other punctuation: "
"\\`\\<c red> this is normal text \\</c>\\`."
)
value = "this string contains <c red>color tags</c>"
yuio.io.info(
"Interpolated values aren't processed: %s",
value,
)
See full list of tags in yuio.theme.
yuio.io.info(
"Color tags are similar to XML: "
"<c bold green>this text is bold and green</c>" # [1]_
)
yuio.io.info(
"Backticks work like in Markdown: "
"`this is an escaped code, tags like <c red> don't work here.`"
)
yuio.io.info(
"You can escape backticks and other punctuation: "
"\\`\\<c red> this is normal text \\</c>\\`."
)
value = "this string contains <c red>color tags</c>"
yuio.io.info(
t"Interpolated values aren't processed: {value}",
)
See full list of tags in yuio.theme.
Pretty-printing Python objects¶
Yuio supports rich repr protocol, which enables you to pretty-print
Python objects. Pretty-printing is controlled through format flags for %r
and %s, as well as through formatting flags for template strings:
#enables colors in repr (i.e.%#r);+splits repr into multiple lines (i.e.%+r,%#+r).
import dataclasses
import yuio.app
import yuio.io
@dataclasses.dataclass
class CoordinateSystem:
origin: tuple[int, int] = (0, 0)
scale: tuple[float, float] = (1.0, 1.0)
@yuio.app.app
def main():
coordinates = CoordinateSystem()
yuio.io.info("Main coordinate system: %#+r", coordinates)
if __name__ == "__main__":
main.run()
#enables colors in repr (i.e.{var:#});+splits repr into multiple lines (i.e.{var:+},{var:#+});these flags work when you format strings or colorable objects: you’ll have to explicitly convert objects of other types to strings by specifying conversion operator, i.e.
{var!r:#}.
import dataclasses
import yuio.app
import yuio.io
@dataclasses.dataclass
class CoordinateSystem:
origin: tuple[int, int] = (0, 0)
scale: tuple[float, float] = (1.0, 1.0)
@yuio.app.app
def main():
coordinates = CoordinateSystem()
yuio.io.info(t"Main coordinate system: {coordinates!r:#+}")
if __name__ == "__main__":
main.run()
", ".join(map(repr, values))¶
You often need to print lists joined by some separator. Yuio provides
JoinStr, JoinRepr,
And, and Or to help with this task:
ALLOWED_VALUES = ["foo", "bar", "baz"]
yuio.io.info(
"Allowed values: %s",
yuio.io.JoinRepr(ALLOWED_VALUES, color="magenta"), # [1]_
)
You can pass multiple color tags in the same string, just separate them with spaces.
ALLOWED_VALUES = ["foo", "bar", "baz"]
yuio.io.info(
t"Allowed values: {yuio.io.JoinRepr(ALLOWED_VALUES, color='magenta')}", # [1]_
)
You can pass multiple color tags in the same string, just separate them with spaces.
RST and Markdown¶
Yuio also supports basic RST and Markdown formatting. It’s mostly used for generating CLI help, but you can print messages with it as well:
yuio.io.rst("""
Greetings!
----------
- You can use *inline formatting*, ``backticks``,
and :py:class:`interpreted text <yuio.md.MdParser>`.
- Hyperlinks `also work`__!
- You can also use some common directives, though not all of them:
.. warning::
Oh, and tables are not supported, at least for now.
- Plus, there's syntax highlighting. For example, check out this fork bomb:
.. code-block:: sh
:(){ :|:& };: # <- don't paste this in bash!
__ https://yuio.readthedocs.io/
""")
yuio.io.md("""
# Greetings!
- You can use *inline formatting*, `backticks`,
and {py:class}`MySt roles <yuio.md.MdParser>`.
- Hyperlinks [also work]!
- You can also use CommonMark block markup and MyST directives:
```{warning}
Tables are not supported, though.
```
- Plus, there's syntax highlighting. For example, check out this fork bomb:
```sh
:(){ :|:& };: # <- don't paste this in bash!
```
[also work]: https://yuio.readthedocs.io/
""")
Highlighting code¶
Yuio supports simple code highlighting:
yuio.io.hl(
"""
{
"version": "1.0.0",
"pre-release": false,
"post-release": false,
}
""",
syntax="json", # [1]_
)
See full list of supported languages in
yuio.hl.
Querying user input¶
You can use yuio.io.ask() to get data from the user. It’s like input(),
but automatically parses the user input, and can use different widgets based
on the expected value’s type:
import enum
import yuio.app
import yuio.io
class GreetingType(enum.Enum): # [1]_
FORMAL = "Formal"
INFORMAL = "Informal"
@yuio.app.app
def main():
name = yuio.io.ask("What's your name?")
greeting_type = yuio.io.ask[GreetingType]( # [2]_
"What kind of greeting do you want?",
default=GreetingType.FORMAL,
)
Note
yuio.io.ask() is designed to interact with users, not to read data. It uses
/dev/tty on Unix, and console API on Windows, so it will read from
an actual TTY even if stdin is redirected.
When designing your program, make sure that users have alternative means to provide values: use configs or CLI arguments, allow passing passwords via environment variables, etc.
Indicating progress¶
Suppose you have some long-running job, and you want to indicate that it is running.
yuio.io.Task to the rescue:
with yuio.io.Task("Sending a greeting"):
send_greeting()
And if the job can report its progress, we can even show a progressbar:
with yuio.io.Task("Sending greetings") as task:
for i, email in enumerate(emails):
task.comment(email)
task.progress(i, len(emails)) # [1]_
send_greeting()
Taskhas lots of helper methods on it.For example, this code can be simplified using
Task.iter()instead ofenumerate().
Opening an external editor¶
You know how when you run git commit, it opens an editor and asks you to edit
a commit message? Yuio can do the same:
greeting = yuio.io.edit(
"# Please, edit the greeting:\n"
"Hello, world!",
comment_marker="#", # [1]_
)
yuio.io.info("Greeting: `%s`", greeting)
All lines that start with this marker will be removed after editing.
Logging¶
The app will automatically perform a basic logging configuration on startup.
The logging level will depend on how many -v flags are given,
from WARNING to DEBUG:
import logging
import yuio.app
logger = logging.getLogger("main")
@yuio.app.app
def main(foo: str = "", bar: str = ""):
logger.debug("settings: foo=%r, bar=%r", foo, bar)
logger.info("Running some logic...")
if __name__ == "__main__":
main.run()
This is how verbose output will look like:
If you want to configure logging yourself, set yuio.app.App.setup_logging
to False, and use yuio.io.Handler to send logs to stderr:
import logging
import yuio.app
import yuio.io
logger = logging.getLogger("main")
@yuio.app.app
def main(foo: str = "", bar: str = ""):
logging.basicConfig(
level=logging.INFO,
handlers=[yuio.io.Handler()],
)
logger.debug("settings: foo=%r, bar=%r", foo, bar)
logger.info("Running some logic...")
main.setup_logging = False
if __name__ == "__main__":
main.run()
Suspending output¶
Sometimes you need to stop all output from your program. Most often this happens
when you want to hand IO control to a subprocess. yuio.io.SuspendOutput
does just that:
with yuio.io.SuspendOutput():
subprocess.check_call(["git", "status"])
Tip
yuio.exec provides a simple wrapper around subprocess that will
log process’ stderr, and return process’ stdout.
yuio.io.SuspendOutput will disable all output, including prints
and writes to sys.stderr and sys.stdout. To bypass it,
use yuio.io.orig_stderr(), yuio.io.orig_stdout(), and methods
on the yuio.io.SuspendOutput class.
Here’s a more comprehensive example:
import time
import subprocess
import yuio.app
import yuio.io
@yuio.app.app
def main():
with yuio.io.Task("Performing some task"):
time.sleep(1)
# All progress bars, prints, and so on are suspended
# inside of this context manager.
with yuio.io.SuspendOutput() as o:
# But you can manually bypass output suspension.
o.info("Running `git status`:")
subprocess.check_call(["git", "status"])
time.sleep(1)