**TL;DR**: This converts a file like this (config file at `~/.config/ttally.py`):
```python
# https://github.com/seanbreckenridge/ttally
from datetime import datetime
from typing import NamedTuple, Optional
class Weight(NamedTuple):
when: datetime
pounds: float
class Food(NamedTuple):
when: datetime
calories: int
food: str
quantity: float
water: int # how much ml of water was in this
@staticmethod
def attr_validators() -> dict:
# https://sean.fish/d/ttally_types.py?redirect
from my.config.seanb.ttally_types import prompt_float_default # type: ignore
# if I don't supply a quantity, default to 1
return {"quantity": lambda: prompt_float_default("quantity")}
class Event(NamedTuple):
"""e.g. a concert or something"""
event_type: str
when: datetime
description: str
score: Optional[int]
comments: Optional[str]
@staticmethod
def attr_validators() -> dict:
from my.config.seanb.ttally_types import edit_in_vim # type: ignore
return {"comments": edit_in_vim}
import os
from enum import Enum
with open(os.path.join(os.environ["HPIDATA"], "self_types.txt")) as f:
SelfTypes = Enum("SelfTypes", [s.rstrip().upper() for s in f])
class Self(NamedTuple):
when: datetime
what: SelfTypes # type: ignore
```
to (shell aliases)...
```
alias event='python3 -m ttally prompt event'
alias event-now='python3 -m ttally prompt-now event'
alias event-recent='python3 -m ttally recent event'
alias food='python3 -m ttally prompt food'
alias food-now='python3 -m ttally prompt-now food'
alias food-recent='python3 -m ttally recent food'
alias self='python3 -m ttally prompt self'
alias self-now='python3 -m ttally prompt-now self'
alias self-recent='python3 -m ttally recent self'
alias weight='python3 -m ttally prompt weight'
alias weight-now='python3 -m ttally prompt-now weight'
alias weight-recent='python3 -m ttally recent weight'
```
Whenever I run any of those aliases, it inspects the model in the config file, and on-the-fly creates and runs an interactive interface like this:
<img src="https://raw.githubusercontent.com/seanbreckenridge/autotui/master/.assets/builtin_demo.gif">
... which saves what I enter to a file:
```yaml
- when: 1598856786,
glasses": 2.0
```
## ttally
`ttally` is an interactive module using [`autotui`](https://github.com/seanbreckenridge/autotui) to save things I do often to YAML/JSON
Currently, I use this to store info like whenever I eat something/drink water/my current weight/thoughts on concerts
Given a `NamedTuple` defined in [`~/.config/ttally.py`](https://sean.fish/d/ttally.py?redirect), this creates interactive interfaces which validates my input and saves it to a file
The `{tuple}-now` aliases set the any `datetime` values for the prompted tuple to now
This also gives me `{tuple}-recent` aliases, which print recent items I've logged. For example:
```
$ water-recent 5
2021-03-20 18:23:24 2.0
2021-03-20 01:28:27 1.0
2021-03-19 23:34:12 1.0
2021-03-19 22:49:05 1.5
2021-03-19 16:05:34 1.0
```
The `-recent` aliases can accept `all` to print all items, or a duration like `1d` or `6h` to print data from the last few hours/days.
## Why/How
### Goals
- validates my user input to basic types
- stores it as a user-editable format (YAML)
- can be loaded into python as typed objects
- minimal boilerplate to add a new model
- can be synced across multiple machines without conflicts
- allow completely custom types or prompts - see [autotui docs](https://github.com/seanbreckenridge/autotui#custom-types), [my custom prompts](https://sean.fish/d/ttally_types.py?redirect)
This intentionally uses YAML and doesn't store the info into a single "merged" database. That way:
- you can just open the YAML file and quickly change/edit some item, no need to re-invent a CRUD interface (though `ttally edit-recent` does exist)
- files can be synced across machines and to my phone using [syncthing](https://syncthing.net/) without file conflicts
- prevents issues with trying to merge multiple databases from different machines together ([I've tried](https://github.com/seanbreckenridge/calories-scripts/blob/master/calmerge))
The YAML files are versioned with the date/OS/platform, so I'm able to add items on my linux, mac, or android (using [`termux`](https://termux.com/)) and sync them across all my devices using [`SyncThing`](https://syncthing.net/). Each device creates its own file it adds items to, like:
```
food-darwin-seans-mbp.localdomain-2021-03.yaml
food-linux-bastion-2021-03.yaml
food-linux-localhost-2021-04.yaml
```
... which can then be combined back into python, like:
```python
>>> from more_itertools import take # just to grab a few items
>>> from ttally.__main__ import ext
>>> from ttally.config import Food
>>> take(3, ext.glob_namedtuple(Food))
[Food(when=datetime.datetime(2020, 9, 27, 6, 49, 34, tzinfo=datetime.timezone.utc), calories=440, food='ramen, egg'),
Food(when=datetime.datetime(2020, 9, 27, 6, 52, 16, tzinfo=datetime.timezone.utc), calories=160, food='2 eggs'),
Food(when=datetime.datetime(2020, 9, 27, 6, 53, 44, tzinfo=datetime.timezone.utc), calories=50, food='ginger chai')]
```
... or into JSON using `ttally export food`
The `from-json` command can be used to send this JSON which matches a model, i.e. providing a non-interactive interface to add items, in case I want to [call this from a script](bin/cz)
`hpi query` from [`HPI`](https://github.com/seanbreckenridge/HPI) can be used with the `ttally.__main__` module, like:
```bash
# how many calories in the last day
$ hpi query ttally.__main__.food --recent 1d -s | jq -r '(.quantity)*(.calories)' | datamash sum 1
2252
```
If you'd prefer to use JSON files, you can set the `TTALLY_EXT=json` environment variable.
This can load data from YAML or JSON (or both at the same time), every couple months I'll combine all the versioned files to a single merged file using the `merge` command:
```
ttally merge food
```
## Installation
```bash
pip install ttally
```
```
Usage: ttally [OPTIONS] COMMAND [ARGS]...
Tally things that I do often!
Given a few namedtuples, this creates serializers/deserializers and an
interactive interface using 'autotui', and aliases to:
prompt using default autotui behavior, writing to the ttally datafile, same
as above, but if the model has a datetime, set it to now, query the 10 most
recent items for a model
Options:
--help Show this message and exit.
Commands:
datafile print the datafile location
edit edit the datafile
edit-recent fuzzy select/edit recent items
export export all data from a model
from-json add item by piping JSON
generate generate shell aliases
merge merge all data for a model into one file
models list models
prompt tally an item
prompt-now tally an item (now)
recent print recently tallied items
update-cache cache export data
```
### Configuration
You need to setup a `~/.config/ttally.py` file. You can use the block above as a starting point, or with mine:
```bash
curl -s 'https://sean.fish/d/ttally.py' > ~/.config/ttally.py
```
To setup aliases; You can do it each time you launch you terminal like:
```bash
eval "$(python3 -m ttally generate)"
```
Or, 'cache' the generated aliases by putting a block like this in your shell config:
```bash
TTALLY_ALIASES="${HOME}/.cache/ttally_aliases"
if [[ ! -e "${TTALLY_ALIASES}" ]]; then # alias file doesn't exist
python3 -m ttally generate >"${TTALLY_ALIASES}" # generate and save the aliases
fi
source "${TTALLY_ALIASES}" # make aliases available in your shell
```
i.e., it runs the first time I open a terminal, but then stays the same until I remove the file
You can set the `TTALLY_DATA_DIR` environment variable to the directory that `ttally` should save data to, defaults to `~/.local/share/ttally`. If you want to use a different path for configuration, you can set the `TTALLY_CFG` to the absolute path to the file.
For shell completion to autocomplete options/model names:
```
eval "$(_TTALLY_COMPLETE=bash_source ttally)" # in ~/.bashrc
eval "$(_TTALLY_COMPLETE=zsh_source ttally)" # in ~/.zshrc
eval "$(_TTALLY_COMPLETE=fish_source ttally)" # in ~/.config/fish/config.fish
```
### Caching
`ttally update-cache` can be used to speedup the `export` and `recent` commands:
```
Usage: ttally update-cache [OPTIONS]
Caches data for 'export' and 'recent' by saving the current data and an
index to ~/.cache/ttally
exit code 0 if cache was updated, 2 if it was already up to date
Options:
--print-hashes print current filehash debug info
--help Show this message and exit.
```
I personally run it [once every 3 minutes](https://sean.fish/d/ttally_cache.job?redirect) in the background, so at least my first interaction with `ttally` is guaranteed to be [fast](https://github.com/seanbreckenridge/ttally/issues/5#issuecomment-1321389800)
Default cache directory can be overwritten with the `TTALLY_CACHE_DIR` environment variable
### Subclassing/Extension
The entire `ttally` library/CLI can also be subclassed/extended for custom usage, by using `ttally.core.Extension` class and `wrap_cli` to add additional [click](https://click.palletsprojects.com/en/8.1.x) commands. For an example, see [flipflop.py](https://sean.fish/d/flipflop.py?redirect)
### Shell Scripts
[`cz`](bin/cz) lets me fuzzy select something I've eaten in the past using [`fzf`](https://github.com/junegunn/fzf), like:
![](https://raw.githubusercontent.com/seanbreckenridge/calories-fzf/master/demo.gif)
Raw data
{
"_id": null,
"home_page": "https://github.com/seanbreckenridge/ttally",
"name": "ttally",
"maintainer": "",
"docs_url": null,
"requires_python": ">=3.8",
"maintainer_email": "",
"keywords": "data,interactive",
"author": "Sean Breckenridge",
"author_email": "\"seanbrecke@gmail.com\"",
"download_url": "https://files.pythonhosted.org/packages/14/fc/cf0ed2cf01d65d58edb05db9a56acbb5867e1b9b501ca3feca24a2a3e89d/ttally-0.1.4.tar.gz",
"platform": null,
"description": "**TL;DR**: This converts a file like this (config file at `~/.config/ttally.py`):\n\n```python\n# https://github.com/seanbreckenridge/ttally\n\nfrom datetime import datetime\nfrom typing import NamedTuple, Optional\n\n\nclass Weight(NamedTuple):\n when: datetime\n pounds: float\n\n\nclass Food(NamedTuple):\n when: datetime\n calories: int\n food: str\n quantity: float\n water: int # how much ml of water was in this\n\n @staticmethod\n def attr_validators() -> dict:\n # https://sean.fish/d/ttally_types.py?redirect\n from my.config.seanb.ttally_types import prompt_float_default # type: ignore\n\n # if I don't supply a quantity, default to 1\n return {\"quantity\": lambda: prompt_float_default(\"quantity\")}\n\n\nclass Event(NamedTuple):\n \"\"\"e.g. a concert or something\"\"\"\n\n event_type: str\n when: datetime\n description: str\n score: Optional[int]\n comments: Optional[str]\n\n @staticmethod\n def attr_validators() -> dict:\n from my.config.seanb.ttally_types import edit_in_vim # type: ignore\n\n return {\"comments\": edit_in_vim}\n\n\nimport os\nfrom enum import Enum\n\nwith open(os.path.join(os.environ[\"HPIDATA\"], \"self_types.txt\")) as f:\n SelfTypes = Enum(\"SelfTypes\", [s.rstrip().upper() for s in f])\n\n\nclass Self(NamedTuple):\n when: datetime\n what: SelfTypes # type: ignore\n```\n\nto (shell aliases)...\n\n```\nalias event='python3 -m ttally prompt event'\nalias event-now='python3 -m ttally prompt-now event'\nalias event-recent='python3 -m ttally recent event'\nalias food='python3 -m ttally prompt food'\nalias food-now='python3 -m ttally prompt-now food'\nalias food-recent='python3 -m ttally recent food'\nalias self='python3 -m ttally prompt self'\nalias self-now='python3 -m ttally prompt-now self'\nalias self-recent='python3 -m ttally recent self'\nalias weight='python3 -m ttally prompt weight'\nalias weight-now='python3 -m ttally prompt-now weight'\nalias weight-recent='python3 -m ttally recent weight'\n```\n\nWhenever I run any of those aliases, it inspects the model in the config file, and on-the-fly creates and runs an interactive interface like this:\n\n<img src=\"https://raw.githubusercontent.com/seanbreckenridge/autotui/master/.assets/builtin_demo.gif\">\n\n... which saves what I enter to a file:\n\n```yaml\n- when: 1598856786,\n glasses\": 2.0\n```\n\n## ttally\n\n`ttally` is an interactive module using [`autotui`](https://github.com/seanbreckenridge/autotui) to save things I do often to YAML/JSON\n\nCurrently, I use this to store info like whenever I eat something/drink water/my current weight/thoughts on concerts\n\nGiven a `NamedTuple` defined in [`~/.config/ttally.py`](https://sean.fish/d/ttally.py?redirect), this creates interactive interfaces which validates my input and saves it to a file\n\nThe `{tuple}-now` aliases set the any `datetime` values for the prompted tuple to now\n\nThis also gives me `{tuple}-recent` aliases, which print recent items I've logged. For example:\n\n```\n$ water-recent 5\n2021-03-20 18:23:24 2.0\n2021-03-20 01:28:27 1.0\n2021-03-19 23:34:12 1.0\n2021-03-19 22:49:05 1.5\n2021-03-19 16:05:34 1.0\n```\n\nThe `-recent` aliases can accept `all` to print all items, or a duration like `1d` or `6h` to print data from the last few hours/days.\n\n## Why/How\n\n### Goals\n\n- validates my user input to basic types\n- stores it as a user-editable format (YAML)\n- can be loaded into python as typed objects\n- minimal boilerplate to add a new model\n- can be synced across multiple machines without conflicts\n- allow completely custom types or prompts - see [autotui docs](https://github.com/seanbreckenridge/autotui#custom-types), [my custom prompts](https://sean.fish/d/ttally_types.py?redirect)\n\nThis intentionally uses YAML and doesn't store the info into a single \"merged\" database. That way:\n\n- you can just open the YAML file and quickly change/edit some item, no need to re-invent a CRUD interface (though `ttally edit-recent` does exist)\n- files can be synced across machines and to my phone using [syncthing](https://syncthing.net/) without file conflicts\n- prevents issues with trying to merge multiple databases from different machines together ([I've tried](https://github.com/seanbreckenridge/calories-scripts/blob/master/calmerge))\n\nThe YAML files are versioned with the date/OS/platform, so I'm able to add items on my linux, mac, or android (using [`termux`](https://termux.com/)) and sync them across all my devices using [`SyncThing`](https://syncthing.net/). Each device creates its own file it adds items to, like:\n\n```\nfood-darwin-seans-mbp.localdomain-2021-03.yaml\nfood-linux-bastion-2021-03.yaml\nfood-linux-localhost-2021-04.yaml\n```\n\n... which can then be combined back into python, like:\n\n```python\n>>> from more_itertools import take # just to grab a few items\n>>> from ttally.__main__ import ext\n>>> from ttally.config import Food\n>>> take(3, ext.glob_namedtuple(Food))\n\n[Food(when=datetime.datetime(2020, 9, 27, 6, 49, 34, tzinfo=datetime.timezone.utc), calories=440, food='ramen, egg'),\nFood(when=datetime.datetime(2020, 9, 27, 6, 52, 16, tzinfo=datetime.timezone.utc), calories=160, food='2 eggs'),\nFood(when=datetime.datetime(2020, 9, 27, 6, 53, 44, tzinfo=datetime.timezone.utc), calories=50, food='ginger chai')]\n```\n\n... or into JSON using `ttally export food`\n\nThe `from-json` command can be used to send this JSON which matches a model, i.e. providing a non-interactive interface to add items, in case I want to [call this from a script](bin/cz)\n\n`hpi query` from [`HPI`](https://github.com/seanbreckenridge/HPI) can be used with the `ttally.__main__` module, like:\n\n```bash\n# how many calories in the last day\n$ hpi query ttally.__main__.food --recent 1d -s | jq -r '(.quantity)*(.calories)' | datamash sum 1\n2252\n```\n\nIf you'd prefer to use JSON files, you can set the `TTALLY_EXT=json` environment variable.\n\nThis can load data from YAML or JSON (or both at the same time), every couple months I'll combine all the versioned files to a single merged file using the `merge` command:\n\n```\nttally merge food\n```\n\n## Installation\n\n```bash\npip install ttally\n```\n\n```\nUsage: ttally [OPTIONS] COMMAND [ARGS]...\n\n Tally things that I do often!\n\n Given a few namedtuples, this creates serializers/deserializers and an\n interactive interface using 'autotui', and aliases to:\n\n prompt using default autotui behavior, writing to the ttally datafile, same\n as above, but if the model has a datetime, set it to now, query the 10 most\n recent items for a model\n\nOptions:\n --help Show this message and exit.\n\nCommands:\n datafile print the datafile location\n edit edit the datafile\n edit-recent fuzzy select/edit recent items\n export export all data from a model\n from-json add item by piping JSON\n generate generate shell aliases\n merge merge all data for a model into one file\n models list models\n prompt tally an item\n prompt-now tally an item (now)\n recent print recently tallied items\n update-cache cache export data\n```\n\n### Configuration\n\nYou need to setup a `~/.config/ttally.py` file. You can use the block above as a starting point, or with mine:\n\n```bash\ncurl -s 'https://sean.fish/d/ttally.py' > ~/.config/ttally.py\n```\n\nTo setup aliases; You can do it each time you launch you terminal like:\n\n```bash\neval \"$(python3 -m ttally generate)\"\n```\n\nOr, 'cache' the generated aliases by putting a block like this in your shell config:\n\n```bash\nTTALLY_ALIASES=\"${HOME}/.cache/ttally_aliases\"\nif [[ ! -e \"${TTALLY_ALIASES}\" ]]; then # alias file doesn't exist\n\tpython3 -m ttally generate >\"${TTALLY_ALIASES}\" # generate and save the aliases\nfi\nsource \"${TTALLY_ALIASES}\" # make aliases available in your shell\n```\n\ni.e., it runs the first time I open a terminal, but then stays the same until I remove the file\n\nYou can set the `TTALLY_DATA_DIR` environment variable to the directory that `ttally` should save data to, defaults to `~/.local/share/ttally`. If you want to use a different path for configuration, you can set the `TTALLY_CFG` to the absolute path to the file.\n\nFor shell completion to autocomplete options/model names:\n\n```\neval \"$(_TTALLY_COMPLETE=bash_source ttally)\" # in ~/.bashrc\neval \"$(_TTALLY_COMPLETE=zsh_source ttally)\" # in ~/.zshrc\neval \"$(_TTALLY_COMPLETE=fish_source ttally)\" # in ~/.config/fish/config.fish\n```\n\n### Caching\n\n`ttally update-cache` can be used to speedup the `export` and `recent` commands:\n\n```\nUsage: ttally update-cache [OPTIONS]\n\n Caches data for 'export' and 'recent' by saving the current data and an\n index to ~/.cache/ttally\n\n exit code 0 if cache was updated, 2 if it was already up to date\n\nOptions:\n --print-hashes print current filehash debug info\n --help Show this message and exit.\n```\n\nI personally run it [once every 3 minutes](https://sean.fish/d/ttally_cache.job?redirect) in the background, so at least my first interaction with `ttally` is guaranteed to be [fast](https://github.com/seanbreckenridge/ttally/issues/5#issuecomment-1321389800)\n\nDefault cache directory can be overwritten with the `TTALLY_CACHE_DIR` environment variable\n\n### Subclassing/Extension\n\nThe entire `ttally` library/CLI can also be subclassed/extended for custom usage, by using `ttally.core.Extension` class and `wrap_cli` to add additional [click](https://click.palletsprojects.com/en/8.1.x) commands. For an example, see [flipflop.py](https://sean.fish/d/flipflop.py?redirect)\n\n### Shell Scripts\n\n[`cz`](bin/cz) lets me fuzzy select something I've eaten in the past using [`fzf`](https://github.com/junegunn/fzf), like:\n\n![](https://raw.githubusercontent.com/seanbreckenridge/calories-fzf/master/demo.gif)\n",
"bugtrack_url": null,
"license": "MIT",
"summary": "interactive module to generate code/aliases to save things I do often",
"version": "0.1.4",
"project_urls": {
"Homepage": "https://github.com/seanbreckenridge/ttally"
},
"split_keywords": [
"data",
"interactive"
],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "09d482729d81fb1721f0008affd40e46213a40b407e43b4691ed41776a8226de",
"md5": "4c791ecf522f5f669df1ea0fc1f3ff24",
"sha256": "5b0cd23c88e90e3fff1a8ec8607fe1b7e1151fcae0caf8171e94facc473826b6"
},
"downloads": -1,
"filename": "ttally-0.1.4-py3-none-any.whl",
"has_sig": false,
"md5_digest": "4c791ecf522f5f669df1ea0fc1f3ff24",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.8",
"size": 18101,
"upload_time": "2023-10-02T09:49:09",
"upload_time_iso_8601": "2023-10-02T09:49:09.007474Z",
"url": "https://files.pythonhosted.org/packages/09/d4/82729d81fb1721f0008affd40e46213a40b407e43b4691ed41776a8226de/ttally-0.1.4-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "14fccf0ed2cf01d65d58edb05db9a56acbb5867e1b9b501ca3feca24a2a3e89d",
"md5": "3b5b1c63edc142b0622cb29beffbacdb",
"sha256": "96f945a4a8f36638d2eeefef0c0e9788916490766d90b6ce5e4c23a01d4036cf"
},
"downloads": -1,
"filename": "ttally-0.1.4.tar.gz",
"has_sig": false,
"md5_digest": "3b5b1c63edc142b0622cb29beffbacdb",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.8",
"size": 22449,
"upload_time": "2023-10-02T09:49:11",
"upload_time_iso_8601": "2023-10-02T09:49:11.003481Z",
"url": "https://files.pythonhosted.org/packages/14/fc/cf0ed2cf01d65d58edb05db9a56acbb5867e1b9b501ca3feca24a2a3e89d/ttally-0.1.4.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2023-10-02 09:49:11",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "seanbreckenridge",
"github_project": "ttally",
"travis_ci": false,
"coveralls": false,
"github_actions": true,
"lcname": "ttally"
}