# autotui
[![PyPi version](https://img.shields.io/pypi/v/autotui.svg)](https://pypi.python.org/pypi/autotui) [![Python 3.8|3.9|3.10](https://img.shields.io/pypi/pyversions/autotui.svg)](https://pypi.python.org/pypi/autotui) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](http://makeapullrequest.com)
This uses type hints to convert [`NamedTuple`](https://docs.python.org/3.9/library/typing.html#typing.NamedTuple)'s (short struct-like classes) to JSON/YAML, and back to python objects.
It also wraps [`prompt_toolkit`](https://python-prompt-toolkit.readthedocs.io/en/master/index.html) to prompt the user and validate the input for common types, and is extendible to whatever types you want.
- [Supported Types](#supported-types)
- [Install](#install)
- [Usage](#usage)
- [Enabling Options](#enabling-options)
- [Partial prompts](#partial-prompts)
- [YAML](#yaml)
- [Picking items](#picking)
- [Editing items](#editing)
- [Custom types/prompting](#custom-types)
- [Testing](#testing)
## Supported Types
This has built-ins to prompt, validate and serialize:
- `int`
- `float`
- `bool`
- `str`
- `datetime`
- `Enum`
- `Decimal`
- `Optional[<type>]` [(or `<type> | None`)](https://www.python.org/dev/peps/pep-0604/)
- `List[<type>]` (or `list[<type>]`)
- `Set[<type>]` (or `set[<type>]`)
- other `NamedTuple`s (recursively)
I wrote this so that I don't have to repeatedly write boilerplate-y python code to validate/serialize/deserialize data. As a more extensive example of its usage, you can see my [`ttally`](https://github.com/seanbreckenridge/ttally) repo, which I use to track things like calories/water etc...
## Install
This requires `python3.8+`, specifically for modern [`typing`](https://docs.python.org/3/library/typing.html) support.
To install with pip, run:
pip install autotui
## Usage
As an example, if I want to log whenever I drink water to a file:
```python
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")
```
<img src="https://raw.githubusercontent.com/seanbreckenridge/autotui/master/.assets/builtin_demo.gif">
Which, after running a few times, would create:
`~/.local/share/water.json`
```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:
```python
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)]
```
A lot of my usage of this only ever uses 3 functions in the [`autotui.shortcuts`](https://github.com/seanbreckenridge/autotui/blob/master/autotui/shortcuts.py) module; `dump_to` to dump a sequence of my `NamedTuple`s to a file, `load_from` to do the opposite, and `load_prompt_and_writeback`, to load values in, prompt me, and write back to the file.
#### Enabling Options
Some options/features can be enabled using global environment variables, or by using a contextmanager to temporarily enable certain prompts/features.
As an example, there are two versions of the `datetime` prompt
- The one you see above using a dialog
- A live version which displays the parsed datetime while typing. Since that can cause some lag, it can be enabled by setting the `LIVE_DATETIME` option.
You can enable that by:
- setting the `AUTOTUI_LIVE_DATETIME` (prefix the name of the option with `AUTOTUI_`) environment variable, e.g., add `export AUTOTUI_LIVE_DATETIME=1` to your `.bashrc`/`.zshrc`
- using the `options` contextmanager:
```python
import autotui
with autotui.options("LIVE_DATETIME"):
autotui.prompt_namedtuple(...)
```
Options:
- `LIVE_DATETIME`: Enables the live datetime prompt
- `CONVERT_UNKNOWN_ENUM_TO_NONE`: If an enum value is not found on the enumeration (e.g. you remove some enum value), convert it to `None` instead of raising a `ValueError`
- `ENUM_FZF`: Use `fzf` to prompt for enums
- `CLICK_PROMPT` - Where possible, use [`click`](https://click.palletsprojects.com/en/8.1.x/) to prompt for values instead of [`prompt_toolkit`](https://python-prompt-toolkit.readthedocs.io/en/master/index.html)
### Partial prompts
If you want to prompt for only a few fields, you can supply the `attr_use_values` or `type_use_values` to supply default values:
```python
# water-now script -- set any datetime values to now
from datetime import datetime
from typing import NamedTuple
from autotui import prompt_namedtuple
from autotui.shortcuts import load_prompt_and_writeback
class Water(NamedTuple):
at: datetime
glass_count: float
load_prompt_and_writeback(Water, "./water.json", type_use_values={datetime: datetime.now()})
# or specify it with a function (don't call datetime.now, just pass the function)
# so its called when its needed
val = prompt_namedtuple(Water, attr_use_values={"at": datetime.now})
```
Since you can specify a function to either of those arguments -- you're free to [write a completely custom prompt function](https://python-prompt-toolkit.readthedocs.io/en/master/pages/asking_for_input.html) to prompt/grab data for that field however you want
For example, to prompt for strings by opening `vim` instead:
```python
from datetime import datetime
from typing import NamedTuple, List, Optional
from autotui.shortcuts import load_prompt_and_writeback
import click
def edit_in_vim() -> str:
m = click.edit(text=None, editor="vim")
return m if m is None else m.strip()
class JournalEntry(NamedTuple):
creation_date: datetime
tags: Optional[List[str]] # one or more tags to tag this journal entry with
content: str
if __name__ == "__main__":
load_prompt_and_writeback(
JournalEntry,
"~/Documents/journal.json",
attr_use_values={"content": edit_in_vim},
)
```
Can also define those as a `staticmethod` on the class, so you don't have to pass around the extra state:
```python
class JournalEntry(NamedTuple):
...
@staticmethod
def attr_use_values() -> Dict:
return {"content": edit_in_vim}
# pulls attr_use_values from the function
prompt_namedtuple(JournalEntry, "~/Documents/journal.json")
```
### Yaml
Since YAML is a superset of JSON, this can also be used with YAML files. `autotui.shortcuts` will automatically decode/write to YAML files based on the file extension.
```python
# using the water example above
if __name__ == "__main__":
load_prompt_and_writeback(Water, "~/.local/share/water.yaml")
```
Results in:
```yaml
- at: 1645840523
glass_count: 1.0
- at: 1645839340
glass_count: 1.0
```
You can also pass `format="yaml"` to the `namedtuple_sequence_dumps/namedtuple_sequence_loads` functions (shown below)
### Picking
This has a basic [`fzf`](https://github.com/junegunn/fzf) picker using [`pyfzf-iter`](https://github.com/seanbreckenridge/pyfzf), which lets you pick one item from a list/iterator:
```python
from autotui import pick_namedtuple
from autotui.shortcuts import load_from
picked = pick_namedtuple(load_from(Water, "~/.local/share/water.json"))
print(picked)
```
To install the required dependencies, install [`fzf`](https://github.com/junegunn/fzf) and `pip install 'autotui[pick]'`
### Editing
This also provides a basic editor, which lets you edit a single field of a `NamedTuple`.
```
$ python3 ./examples/edit.py
Water(at=datetime.datetime(2023, 3, 5, 18, 55, 59, 519320), glass_count=1)
Which field to edit:
1. at
2. glass_count
'glass_count' (float) > 30
Water(at=datetime.datetime(2023, 3, 5, 18, 55, 59, 519320), glass_count=30.0)
```
In python:
```python
from autotui.edit import edit_namedtuple
water = edit_namedtuple(water, print_namedtuple=True)
# can also 'loop', to edit multiple fields
water = edit_namedtuple(water, print_namedtuple=True, loop=True)
```
Any additional arguments to `edit_namedtuple` are passed to `prompt_namedtuple`, so you can specify `type_validators` to `attr_validators` to prompt in some custom way
To install, `pip install 'autotui[edit]'` or `pip install click`
### Custom Types
If you want to support custom types, or specify a special way to serialize another NamedTuple recursively, you can specify `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`](https://docs.python.org/3.8/library/datetime.html#datetime.timedelta) (duration of time), being entered as MM:SS, and the corresponding serializers.
```python
# see examples/timedelta_serializer.py for imports
# handle validating the user input interactively
# 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 python value being serialized
# or the JSON value being 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 that's 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 `json.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 there's a deserializer supplied, it uses that. This is meant to help facilitate quick TUIs, I don't want to have to fight with it.
(If you know what you're doing and want to ignore those warnings, you can set the `AUTOTUI_DISABLE_WARNINGS=1` environment variable)
There are lots of examples on how this is handled/edge-cases in the [`tests`](./tests/test_autotui.py).
You can also take a look at the [`examples`](./examples)
# Testing
```bash
git clone https://github.com/seanbreckenridge/autotui
cd ./autotui
pip install '.[testing]'
mypy ./autotui
pytest
```
Raw data
{
"_id": null,
"home_page": "https://github.com/seanbreckenridge/autotui",
"name": "autotui",
"maintainer": "",
"docs_url": null,
"requires_python": ">=3.8",
"maintainer_email": "",
"keywords": "data prompt namedtuple",
"author": "Sean Breckenridge",
"author_email": "\"seanbrecke@gmail.com\"",
"download_url": "https://files.pythonhosted.org/packages/5e/f6/616f04c40e99a4abbe97568d14b866e68255352769b5041815ba94daa43a/autotui-0.4.7.tar.gz",
"platform": null,
"description": "# autotui\n\n[![PyPi version](https://img.shields.io/pypi/v/autotui.svg)](https://pypi.python.org/pypi/autotui) [![Python 3.8|3.9|3.10](https://img.shields.io/pypi/pyversions/autotui.svg)](https://pypi.python.org/pypi/autotui) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](http://makeapullrequest.com)\n\nThis uses type hints to convert [`NamedTuple`](https://docs.python.org/3.9/library/typing.html#typing.NamedTuple)'s (short struct-like classes) to JSON/YAML, and back to python objects.\n\nIt also wraps [`prompt_toolkit`](https://python-prompt-toolkit.readthedocs.io/en/master/index.html) to prompt the user and validate the input for common types, and is extendible to whatever types you want.\n\n- [Supported Types](#supported-types)\n- [Install](#install)\n- [Usage](#usage)\n - [Enabling Options](#enabling-options)\n - [Partial prompts](#partial-prompts)\n - [YAML](#yaml)\n - [Picking items](#picking)\n - [Editing items](#editing)\n - [Custom types/prompting](#custom-types)\n- [Testing](#testing)\n\n## Supported Types\n\nThis has built-ins to prompt, validate and serialize:\n\n- `int`\n- `float`\n- `bool`\n- `str`\n- `datetime`\n- `Enum`\n- `Decimal`\n- `Optional[<type>]` [(or `<type> | None`)](https://www.python.org/dev/peps/pep-0604/)\n- `List[<type>]` (or `list[<type>]`)\n- `Set[<type>]` (or `set[<type>]`)\n- other `NamedTuple`s (recursively)\n\nI wrote this so that I don't have to repeatedly write boilerplate-y python code to validate/serialize/deserialize data. As a more extensive example of its usage, you can see my [`ttally`](https://github.com/seanbreckenridge/ttally) repo, which I use to track things like calories/water etc...\n\n## Install\n\nThis requires `python3.8+`, specifically for modern [`typing`](https://docs.python.org/3/library/typing.html) support.\n\nTo install with pip, run:\n\n pip install autotui\n\n## Usage\n\nAs an example, if I want to log whenever I drink water to a file:\n\n```python\nfrom datetime import datetime\nfrom typing import NamedTuple\n\nfrom autotui.shortcuts import load_prompt_and_writeback\n\nclass Water(NamedTuple):\n at: datetime\n glass_count: float\n\nif __name__ == \"__main__\":\n load_prompt_and_writeback(Water, \"~/.local/share/water.json\")\n```\n\n<img src=\"https://raw.githubusercontent.com/seanbreckenridge/autotui/master/.assets/builtin_demo.gif\">\n\nWhich, after running a few times, would create:\n\n`~/.local/share/water.json`\n\n```json\n[\n {\n \"at\": 1598856786,\n \"glass_count\": 2.0\n },\n {\n \"at\": 1598856800,\n \"glass_count\": 1.0\n }\n]\n```\n\n_(datetimes are serialized into epoch time)_\n\nIf I want to load the values back into python, its just:\n\n```python\nfrom autotui.shortcuts import load_from\n\nclass Water(NamedTuple):\n #... (same as above)\n\nif __name__ == \"__main__\":\n print(load_from(Water, \"~/.local/share/water.json\"))\n\n#[Water(at=datetime.datetime(2020, 8, 31, 6, 53, 6, tzinfo=datetime.timezone.utc), glass_count=2.0),\n# Water(at=datetime.datetime(2020, 8, 31, 6, 53, 20, tzinfo=datetime.timezone.utc), glass_count=1.0)]\n```\n\nA lot of my usage of this only ever uses 3 functions in the [`autotui.shortcuts`](https://github.com/seanbreckenridge/autotui/blob/master/autotui/shortcuts.py) module; `dump_to` to dump a sequence of my `NamedTuple`s to a file, `load_from` to do the opposite, and `load_prompt_and_writeback`, to load values in, prompt me, and write back to the file.\n\n#### Enabling Options\n\nSome options/features can be enabled using global environment variables, or by using a contextmanager to temporarily enable certain prompts/features.\n\nAs an example, there are two versions of the `datetime` prompt\n\n- The one you see above using a dialog\n- A live version which displays the parsed datetime while typing. Since that can cause some lag, it can be enabled by setting the `LIVE_DATETIME` option.\n\nYou can enable that by:\n\n- setting the `AUTOTUI_LIVE_DATETIME` (prefix the name of the option with `AUTOTUI_`) environment variable, e.g., add `export AUTOTUI_LIVE_DATETIME=1` to your `.bashrc`/`.zshrc`\n- using the `options` contextmanager:\n\n```python\nimport autotui\n\nwith autotui.options(\"LIVE_DATETIME\"):\n autotui.prompt_namedtuple(...)\n```\n\nOptions:\n\n- `LIVE_DATETIME`: Enables the live datetime prompt\n- `CONVERT_UNKNOWN_ENUM_TO_NONE`: If an enum value is not found on the enumeration (e.g. you remove some enum value), convert it to `None` instead of raising a `ValueError`\n- `ENUM_FZF`: Use `fzf` to prompt for enums\n- `CLICK_PROMPT` - Where possible, use [`click`](https://click.palletsprojects.com/en/8.1.x/) to prompt for values instead of [`prompt_toolkit`](https://python-prompt-toolkit.readthedocs.io/en/master/index.html)\n\n### Partial prompts\n\nIf you want to prompt for only a few fields, you can supply the `attr_use_values` or `type_use_values` to supply default values:\n\n```python\n# water-now script -- set any datetime values to now\nfrom datetime import datetime\nfrom typing import NamedTuple\n\nfrom autotui import prompt_namedtuple\nfrom autotui.shortcuts import load_prompt_and_writeback\n\nclass Water(NamedTuple):\n at: datetime\n glass_count: float\n\nload_prompt_and_writeback(Water, \"./water.json\", type_use_values={datetime: datetime.now()})\n# or specify it with a function (don't call datetime.now, just pass the function)\n# so its called when its needed\nval = prompt_namedtuple(Water, attr_use_values={\"at\": datetime.now})\n```\n\nSince you can specify a function to either of those arguments -- you're free to [write a completely custom prompt function](https://python-prompt-toolkit.readthedocs.io/en/master/pages/asking_for_input.html) to prompt/grab data for that field however you want\n\nFor example, to prompt for strings by opening `vim` instead:\n\n```python\nfrom datetime import datetime\nfrom typing import NamedTuple, List, Optional\n\nfrom autotui.shortcuts import load_prompt_and_writeback\n\nimport click\n\n\ndef edit_in_vim() -> str:\n m = click.edit(text=None, editor=\"vim\")\n return m if m is None else m.strip()\n\n\nclass JournalEntry(NamedTuple):\n creation_date: datetime\n tags: Optional[List[str]] # one or more tags to tag this journal entry with\n content: str\n\n\nif __name__ == \"__main__\":\n load_prompt_and_writeback(\n JournalEntry,\n \"~/Documents/journal.json\",\n attr_use_values={\"content\": edit_in_vim},\n )\n```\n\nCan also define those as a `staticmethod` on the class, so you don't have to pass around the extra state:\n\n```python\nclass JournalEntry(NamedTuple):\n ...\n\n @staticmethod\n def attr_use_values() -> Dict:\n return {\"content\": edit_in_vim}\n\n\n# pulls attr_use_values from the function\nprompt_namedtuple(JournalEntry, \"~/Documents/journal.json\")\n```\n\n### Yaml\n\nSince YAML is a superset of JSON, this can also be used with YAML files. `autotui.shortcuts` will automatically decode/write to YAML files based on the file extension.\n\n```python\n# using the water example above\nif __name__ == \"__main__\":\n load_prompt_and_writeback(Water, \"~/.local/share/water.yaml\")\n```\n\nResults in:\n\n```yaml\n- at: 1645840523\n glass_count: 1.0\n- at: 1645839340\n glass_count: 1.0\n```\n\nYou can also pass `format=\"yaml\"` to the `namedtuple_sequence_dumps/namedtuple_sequence_loads` functions (shown below)\n\n### Picking\n\nThis has a basic [`fzf`](https://github.com/junegunn/fzf) picker using [`pyfzf-iter`](https://github.com/seanbreckenridge/pyfzf), which lets you pick one item from a list/iterator:\n\n```python\nfrom autotui import pick_namedtuple\nfrom autotui.shortcuts import load_from\n\npicked = pick_namedtuple(load_from(Water, \"~/.local/share/water.json\"))\nprint(picked)\n```\n\nTo install the required dependencies, install [`fzf`](https://github.com/junegunn/fzf) and `pip install 'autotui[pick]'`\n\n### Editing\n\nThis also provides a basic editor, which lets you edit a single field of a `NamedTuple`.\n\n```\n$ python3 ./examples/edit.py\nWater(at=datetime.datetime(2023, 3, 5, 18, 55, 59, 519320), glass_count=1)\nWhich field to edit:\n\n\t1. at\n\t2. glass_count\n\n'glass_count' (float) > 30\nWater(at=datetime.datetime(2023, 3, 5, 18, 55, 59, 519320), glass_count=30.0)\n```\n\nIn python:\n\n```python\nfrom autotui.edit import edit_namedtuple\n\nwater = edit_namedtuple(water, print_namedtuple=True)\n# can also 'loop', to edit multiple fields\nwater = edit_namedtuple(water, print_namedtuple=True, loop=True)\n```\n\nAny additional arguments to `edit_namedtuple` are passed to `prompt_namedtuple`, so you can specify `type_validators` to `attr_validators` to prompt in some custom way\n\nTo install, `pip install 'autotui[edit]'` or `pip install click`\n\n### Custom Types\n\nIf you want to support custom types, or specify a special way to serialize another NamedTuple recursively, you can specify `type_validators`, and `type_[de]serializer` to handle the validation, serialization, deserialization for that type/attribute name.\n\nAs a more complicated example, heres a validator for [`timedelta`](https://docs.python.org/3.8/library/datetime.html#datetime.timedelta) (duration of time), being entered as MM:SS, and the corresponding serializers.\n\n```python\n# see examples/timedelta_serializer.py for imports\n\n# handle validating the user input interactively\n# can throw a ValueError\ndef _timedelta(user_input: str) -> timedelta:\n if len(user_input.strip()) == 0:\n raise ValueError(\"Not enough input!\")\n minutes, _, seconds = user_input.partition(\":\")\n # could throw ValueError\n return timedelta(minutes=float(minutes), seconds=float(seconds))\n\n\n# serializer for timedelta, converts to JSON-compatible integer\ndef to_seconds(t: timedelta) -> int:\n return int(t.total_seconds())\n\n\n# deserializer from integer to timedelta\ndef from_seconds(seconds: int) -> timedelta:\n return timedelta(seconds=seconds)\n\n\n# The data we want to persist to the file\nclass Action(NamedTuple):\n name: str\n duration: timedelta\n\n\n# AutoHandler describes what function to use to validate\n# user input, and which errors to wrap while validating\ntimedelta_handler = AutoHandler(\n func=_timedelta, # accepts the string the user is typing as input\n catch_errors=[ValueError],\n)\n\n# Note: validators are of type\n# Dict[Type, AutoHandler]\n# serializer/deserializers are\n# Dict[Type, Callable]\n# the Callable accepts one argument,\n# which is either the python value being serialized\n# or the JSON value being deserialized\n\n# use the validator to prompt the user for the NamedTuple data\n# name: str automatically uses a generic string prompt\n# duration: timedelta gets handled by the type_validator\na = prompt_namedtuple(\n Action,\n type_validators={\n timedelta: timedelta_handler,\n },\n)\n\n\n# Note: this specifies timedelta as the type,\n# not int. It uses what the NamedTuple\n# specifies as the type for that field, not\n# the type of the value that's loaded from JSON\n\n# dump to JSON\na_str: str = namedtuple_sequence_dumps(\n [a],\n type_serializers={\n timedelta: to_seconds,\n },\n indent=None,\n)\n\n# load from JSON\na_load = namedtuple_sequence_loads(\n a_str,\n to=Action,\n type_deserializers={\n timedelta: from_seconds,\n },\n)[0]\n\n# can also specify with attributes instead of types\na_load2 = namedtuple_sequence_loads(\n a_str,\n to=Action,\n attr_deserializers={\n \"duration\": from_seconds,\n },\n)[0]\n\nprint(a)\nprint(a_str)\nprint(a_load)\nprint(a_load2)\n```\n\nOutput:\n\n```\n$ python3 ./examples/timedelta_serializer.py\n'name' (str) > on the bus\n'duration' (_timedelta) > 30:00\nAction(name='on the bus', duration=datetime.timedelta(seconds=1800))\n[{\"name\": \"on the bus\", \"duration\": 1800}]\nAction(name='on the bus', duration=datetime.timedelta(seconds=1800))\nAction(name='on the bus', duration=datetime.timedelta(seconds=1800))\n```\n\nThe 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 `json.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 there's a deserializer supplied, it uses that. This is meant to help facilitate quick TUIs, I don't want to have to fight with it.\n\n(If you know what you're doing and want to ignore those warnings, you can set the `AUTOTUI_DISABLE_WARNINGS=1` environment variable)\n\nThere are lots of examples on how this is handled/edge-cases in the [`tests`](./tests/test_autotui.py).\n\nYou can also take a look at the [`examples`](./examples)\n\n# Testing\n\n```bash\ngit clone https://github.com/seanbreckenridge/autotui\ncd ./autotui\npip install '.[testing]'\nmypy ./autotui\npytest\n```\n",
"bugtrack_url": null,
"license": "MIT",
"summary": "\"quickly create UIs to interactively prompt, validate, and persist python objects to disk (JSON/YAML) and back using type hints\"",
"version": "0.4.7",
"project_urls": {
"Homepage": "https://github.com/seanbreckenridge/autotui"
},
"split_keywords": [
"data",
"prompt",
"namedtuple"
],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "d00efcb991a19bd6253dc6c4752cb03ba3264a66f01a5d0981cd2d42446ee45b",
"md5": "a221270041f1acfca37a53d16068a98c",
"sha256": "7b1fc9ebb755f4735d3fc315943db08e6794d878f8302ab2fc9653aadc46c45d"
},
"downloads": -1,
"filename": "autotui-0.4.7-py3-none-any.whl",
"has_sig": false,
"md5_digest": "a221270041f1acfca37a53d16068a98c",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.8",
"size": 27955,
"upload_time": "2024-03-10T06:02:11",
"upload_time_iso_8601": "2024-03-10T06:02:11.449055Z",
"url": "https://files.pythonhosted.org/packages/d0/0e/fcb991a19bd6253dc6c4752cb03ba3264a66f01a5d0981cd2d42446ee45b/autotui-0.4.7-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "5ef6616f04c40e99a4abbe97568d14b866e68255352769b5041815ba94daa43a",
"md5": "2aea9577e73d51074eaf5e2145859b68",
"sha256": "a5c34d7853687054cbab64fa76f580b9f32fe5477d35c17050a232ef9773ab9f"
},
"downloads": -1,
"filename": "autotui-0.4.7.tar.gz",
"has_sig": false,
"md5_digest": "2aea9577e73d51074eaf5e2145859b68",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.8",
"size": 34334,
"upload_time": "2024-03-10T06:02:13",
"upload_time_iso_8601": "2024-03-10T06:02:13.664319Z",
"url": "https://files.pythonhosted.org/packages/5e/f6/616f04c40e99a4abbe97568d14b866e68255352769b5041815ba94daa43a/autotui-0.4.7.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2024-03-10 06:02:13",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "seanbreckenridge",
"github_project": "autotui",
"travis_ci": false,
"coveralls": false,
"github_actions": true,
"lcname": "autotui"
}