helpers for creating TUIs with persistent typed data
Project description
autotui
This uses type hints to convert NamedTuple's to JSON, and back to python objects.
It also wraps prompt_toolkit to prompt the user and validate the input for common types, and is extendible to whatever types you want.
This has built-ins to prompt, validate and serialize:
intfloatboolstrdatetimeOptional[<type>]List[<type>]Set[<type>]
Note: Doesn't support all of these recursively, see below for more info.
I wrote this so that I don't have to repeatedly write boilerplate-y python code to validate/serialize/deserialize data.
As an example, if I want to log whenever I drink water to a file:
from datetime import datetime
from typing import NamedTuple
from autotui.shortcuts import load_prompt_and_writeback
class Water(NamedTuple):
at: datetime
glass_count: float
if __name__ == "__main__":
load_prompt_and_writeback(Water, "~/.local/share/water.json")
Which, after running a few times, would create:
~/.local/share/water.json
[
{
"at": 1598856786,
"glass_count": 2.0
},
{
"at": 1598856800,
"glass_count": 1.0
}
]
(datetimes are serialized into epoch time)
If I want to load the values back into python, its just:
from autotui.shortcuts import load_from
class Water(NamedTuple):
#... (same as above)
if __name__ == "__main__":
print(load_from(Water, "~/.local/share/water.json"))
#[Water(at=datetime.datetime(2020, 8, 31, 6, 53, 6, tzinfo=datetime.timezone.utc), glass_count=2.0),
# Water(at=datetime.datetime(2020, 8, 31, 6, 53, 20, tzinfo=datetime.timezone.utc), glass_count=1.0)]
Installation
This requires python3.8+, specifically for modern typing support.
To install with pip, run:
pip3 install autotui
pip3 install 'autotui[optional]' # to install dateparser, for parsing human-readable times
Custom Types
If your algebraic data type is getting too complicated and autotui can't parse it, you can always specify another NamedTuple or type, and pass a type_validators, and type_[de]serializer to handle the validation, serialization, deserialization for that type/attribute name.
As a more complicated example, heres a validator for timedelta (duration of time), being entered as MM:SS, and the corresponding serializers.
# see examples/timedelta_serializer.py for imports
# handle validating the user input
# can throw a ValueError
def _timedelta(user_input: str) -> timedelta:
if len(user_input.strip()) == 0:
raise ValueError("Not enough input!")
minutes, _, seconds = user_input.partition(":")
# could throw ValueError
return timedelta(minutes=float(minutes), seconds=float(seconds))
# serializer for timedelta, converts to JSON-compatible integer
def to_seconds(t: timedelta) -> int:
return int(t.total_seconds())
# deserializer from integer to timedelta
def from_seconds(seconds: int) -> timedelta:
return timedelta(seconds=seconds)
# The data we want to persist to the file
class Action(NamedTuple):
name: str
duration: timedelta
# AutoHandler describes what function to use to validate
# user input, and which errors to wrap while validating
timedelta_handler = AutoHandler(
func=_timedelta, # accepts the string the user is typing as input
catch_errors=[ValueError],
)
# Note: validators are of type
# Dict[Type, AutoHandler]
# serializer/deserializers are
# Dict[Type, Callable]
# the Callable accepts one argument,
# which is either the type being serialized
# or deserialized
# use the validator to prompt the user for the NamedTuple data
# name: str automatically uses a generic string prompt
# duration: timedelta gets handled by the type_validator
a = prompt_namedtuple(
Action,
type_validators={
timedelta: timedelta_handler,
},
)
# Note: this specifies timedelta as the type,
# not int. It uses what the NamedTuple
# specifies as the type for that field, not
# the type of the value thats loaded from JSON
# dump to JSON
a_str: str = namedtuple_sequence_dumps(
[a],
type_serializers={
timedelta: to_seconds,
},
indent=None,
)
# load from JSON
a_load = namedtuple_sequence_loads(
a_str,
to=Action,
type_deserializers={
timedelta: from_seconds,
},
)[0]
# can also specify with attributes instead of types
a_load2 = namedtuple_sequence_loads(
a_str,
to=Action,
attr_deserializers={
"duration": from_seconds,
},
)[0]
print(a)
print(a_str)
print(a_load)
print(a_load2)
Output:
$ python3 ./examples/timedelta_serializer.py
'name' (str) > on the bus
'duration' (_timedelta) > 30:00
Action(name='on the bus', duration=datetime.timedelta(seconds=1800))
[{"name": "on the bus", "duration": 1800}]
Action(name='on the bus', duration=datetime.timedelta(seconds=1800))
Action(name='on the bus', duration=datetime.timedelta(seconds=1800))
The general philosophy I've taken for serialization and deserialization is send a warning if the types aren't what the NamedTuple expects, but load the values anyways. If serialization can't serialize something, it warns, and if simplejson.dump doesn't have a way to handle it, it throws an error. When deserializing, all values are loaded from their JSON primitives, and then converted into their corresponding python equivalents; If the value doesn't exist, it warns and sets it to None, if theres a deserializer supplied, it uses that. This is meant to help facilitate quick TUIs, I don't want to have to fight with it.
Theres lots of examples on how this is handled/edge-cases in the tests.
You can also take a look at the examples
Tests
pip3 install 'autotui[testing]'
pytest # in the root directory
pytest --doctest-modules ./autotui
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
File details
Details for the file autotui-0.1.2.tar.gz.
File metadata
- Download URL: autotui-0.1.2.tar.gz
- Upload date:
- Size: 16.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/3.2.0 pkginfo/1.5.0.1 requests/2.24.0 setuptools/50.0.0 requests-toolbelt/0.9.1 tqdm/4.48.2 CPython/3.8.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
cfa69a75037d09c1dbc39f5c55e7a9f1787e978eb7c2b74f69505a5f956de3a7
|
|
| MD5 |
b3a0f45cd59b01e0607088abf0a0c177
|
|
| BLAKE2b-256 |
3dfdcf3ae979300420ef3b8785e067b2eaff8b4db67b0f42ac63664b6601f66e
|