timeo 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- timeo-0.2.0/.github/workflows/ci.yml +31 -0
- timeo-0.2.0/.github/workflows/publish.yml +24 -0
- timeo-0.2.0/.github/workflows/release.yml +20 -0
- timeo-0.2.0/.gitignore +50 -0
- timeo-0.2.0/.pre-commit-config.yaml +11 -0
- timeo-0.2.0/.python-version +1 -0
- timeo-0.2.0/CHANGELOG.md +61 -0
- timeo-0.2.0/CLAUDE.md +206 -0
- timeo-0.2.0/CONVENTIONS.md +171 -0
- timeo-0.2.0/LICENSE +21 -0
- timeo-0.2.0/PKG-INFO +200 -0
- timeo-0.2.0/README.md +178 -0
- timeo-0.2.0/flake-explained.md +99 -0
- timeo-0.2.0/flake.lock +212 -0
- timeo-0.2.0/flake.nix +145 -0
- timeo-0.2.0/pyproject.toml +41 -0
- timeo-0.2.0/timeo/__init__.py +21 -0
- timeo-0.2.0/timeo/cache.py +139 -0
- timeo-0.2.0/timeo/decorator.py +137 -0
- timeo-0.2.0/timeo/hashing.py +20 -0
- timeo-0.2.0/timeo/manager.py +196 -0
- timeo-0.2.0/timeo/py.typed +0 -0
- timeo-0.2.0/timeo/task.py +40 -0
- timeo-0.2.0/uv.lock +303 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main, dev]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main, dev]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
test:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
strategy:
|
|
13
|
+
fail-fast: false
|
|
14
|
+
matrix:
|
|
15
|
+
python-version: ["3.9", "3.10", "3.11", "3.12"]
|
|
16
|
+
|
|
17
|
+
steps:
|
|
18
|
+
- uses: actions/checkout@v4
|
|
19
|
+
|
|
20
|
+
- uses: astral-sh/setup-uv@v5
|
|
21
|
+
with:
|
|
22
|
+
python-version: ${{ matrix.python-version }}
|
|
23
|
+
|
|
24
|
+
- name: Install dependencies
|
|
25
|
+
run: uv sync --all-groups
|
|
26
|
+
|
|
27
|
+
- name: Run pre-commit
|
|
28
|
+
run: uv run pre-commit run --all-files
|
|
29
|
+
|
|
30
|
+
- name: Run tests
|
|
31
|
+
run: uv run pytest
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published, released]
|
|
6
|
+
workflow_dispatch:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
publish:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
environment: pypi
|
|
12
|
+
permissions:
|
|
13
|
+
id-token: write # required for OIDC trusted publishing
|
|
14
|
+
|
|
15
|
+
steps:
|
|
16
|
+
- uses: actions/checkout@v4
|
|
17
|
+
|
|
18
|
+
- uses: astral-sh/setup-uv@v5
|
|
19
|
+
|
|
20
|
+
- name: Build package
|
|
21
|
+
run: uv build
|
|
22
|
+
|
|
23
|
+
- name: Publish to PyPI
|
|
24
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
name: release-please
|
|
2
|
+
on:
|
|
3
|
+
push:
|
|
4
|
+
branches:
|
|
5
|
+
- main
|
|
6
|
+
permissions:
|
|
7
|
+
contents: write # to push tags and changelog
|
|
8
|
+
issues: write # (optional) for any issue linkage
|
|
9
|
+
pull-requests: write # to create/modify the release PR
|
|
10
|
+
jobs:
|
|
11
|
+
release:
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
steps:
|
|
14
|
+
- uses: actions/checkout@v4
|
|
15
|
+
- uses: googleapis/release-please-action@v4
|
|
16
|
+
with:
|
|
17
|
+
# Use a GitHub token or personal access token:
|
|
18
|
+
token: ${{ secrets.GITHUB_TOKEN }}
|
|
19
|
+
# A simple release strategy (no code version file):
|
|
20
|
+
release-type: python
|
timeo-0.2.0/.gitignore
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*.pyo
|
|
5
|
+
*.pyd
|
|
6
|
+
*.so
|
|
7
|
+
|
|
8
|
+
# Virtual environments
|
|
9
|
+
.venv/
|
|
10
|
+
venv/
|
|
11
|
+
env/
|
|
12
|
+
|
|
13
|
+
# Distribution / packaging
|
|
14
|
+
dist/
|
|
15
|
+
build/
|
|
16
|
+
*.egg-info/
|
|
17
|
+
*.egg
|
|
18
|
+
.eggs/
|
|
19
|
+
|
|
20
|
+
# uv
|
|
21
|
+
.uv/
|
|
22
|
+
uv.lock # optional — commit this if you want reproducible installs
|
|
23
|
+
|
|
24
|
+
# Testing
|
|
25
|
+
.pytest_cache/
|
|
26
|
+
.coverage
|
|
27
|
+
htmlcov/
|
|
28
|
+
.tox/
|
|
29
|
+
|
|
30
|
+
# Type checking
|
|
31
|
+
.mypy_cache/
|
|
32
|
+
.ruff_cache/
|
|
33
|
+
|
|
34
|
+
# Jupyter
|
|
35
|
+
.ipynb_checkpoints/
|
|
36
|
+
|
|
37
|
+
# Editors
|
|
38
|
+
.vscode/
|
|
39
|
+
.idea/
|
|
40
|
+
*.swp
|
|
41
|
+
*.swo
|
|
42
|
+
|
|
43
|
+
# OS
|
|
44
|
+
.DS_Store
|
|
45
|
+
Thumbs.db
|
|
46
|
+
|
|
47
|
+
# Environment variables
|
|
48
|
+
.env
|
|
49
|
+
.env.*
|
|
50
|
+
!.env.example
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.9
|
timeo-0.2.0/CHANGELOG.md
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## [0.2.0](https://github.com/wtewalt/timeo/compare/v0.1.0...v0.2.0) (2026-04-09)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* add smoke test scripts for sequential, concurrent, and live con… ([92f52a8](https://github.com/wtewalt/timeo/commit/92f52a8f7d5a658afd17d9df0abb33ef95f479f7))
|
|
9
|
+
* add smoke test scripts for sequential, concurrent, and live context modes ([6f14c94](https://github.com/wtewalt/timeo/commit/6f14c94cc424ea4afd1885f674e21e395b5e3007))
|
|
10
|
+
* add test suite for task, hashing, cache, decorator, and learn mode ([754fd86](https://github.com/wtewalt/timeo/commit/754fd86a95280c2e7312ba0a02cb79162531d2aa))
|
|
11
|
+
* implement [@timeo](https://github.com/timeo).track decorator, advance(), and iter() ([8894dbf](https://github.com/wtewalt/timeo/commit/8894dbfe853b4e57ef35a4fed97202f4a07107d5))
|
|
12
|
+
* implement learn mode (learn=True) with EMA-driven progress bar ([946ea49](https://github.com/wtewalt/timeo/commit/946ea49b60fe3d512cf4a10f0922193b3479d9cc))
|
|
13
|
+
* implement learn mode (learn=True) with EMA-driven progress bar ([dda6553](https://github.com/wtewalt/timeo/commit/dda6553476345e62bea4dda0943e6392c14b0fda))
|
|
14
|
+
* implement ProgressManager singleton ([d352bb8](https://github.com/wtewalt/timeo/commit/d352bb87be0baddae592e02359bc90e8456c22d2))
|
|
15
|
+
* implement ProgressManager singleton ([83a9151](https://github.com/wtewalt/timeo/commit/83a91510eb4643f4ab7a0e78bfe4d7463af18fec))
|
|
16
|
+
* implement timing cache and function bytecode hashing ([9bf46fb](https://github.com/wtewalt/timeo/commit/9bf46fb491da12e616f9a5582fe5f3ee91d24012))
|
|
17
|
+
* implement timing cache and function bytecode hashing ([7942968](https://github.com/wtewalt/timeo/commit/7942968c7bbb73b5cd290af1b4258677d9550192))
|
|
18
|
+
* implement TrackedTask model ([b7421e3](https://github.com/wtewalt/timeo/commit/b7421e39146bbfdfcf3a9997a86341486cf8f994))
|
|
19
|
+
* scaffold timeo package structure ([fd042bb](https://github.com/wtewalt/timeo/commit/fd042bbb2f2e5e5c0ffca2d2affa9d09875b6cfe))
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
### Bug Fixes
|
|
23
|
+
|
|
24
|
+
* add future annotations import to task.py for Python 3.9 compat ([6fc84f3](https://github.com/wtewalt/timeo/commit/6fc84f318a5ab8c7ea58bbc608eb308e5832ac39))
|
|
25
|
+
* add future annotations import to task.py for Python 3.9 compat ([e365cf7](https://github.com/wtewalt/timeo/commit/e365cf793dde69fd8faf8aae6c3a640e2a970225))
|
|
26
|
+
* restore ProgressManager implementation on feat/track-decorator branch ([7366fc7](https://github.com/wtewalt/timeo/commit/7366fc7f823f9f890f8a50f3c26301a1b63e3ebc))
|
|
27
|
+
* updated script for initial test ([a02281d](https://github.com/wtewalt/timeo/commit/a02281de1bb0766b0d42d8cbeab4212a8ab11776))
|
|
28
|
+
* use endswith for qualname assertion in learn mode test ([893dda8](https://github.com/wtewalt/timeo/commit/893dda81955ba21af89fc91d318dac303b7673cb))
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
### Documentation
|
|
32
|
+
|
|
33
|
+
* add CLAUDE.md with project overview and architecture ([369a524](https://github.com/wtewalt/timeo/commit/369a524e7f39c620018b337653b06c6d9b5bc267))
|
|
34
|
+
* add CONVENTIONS.md with commit, branch, pre-commit, and release standards ([2cdb283](https://github.com/wtewalt/timeo/commit/2cdb283ce5b0ca844eaaa6426973cf762e553ddf))
|
|
35
|
+
* add nix shell requirement to development environment section ([7a2b2ec](https://github.com/wtewalt/timeo/commit/7a2b2ec825e755c0490d1bfe7508fc2a5f90e7dc))
|
|
36
|
+
* add stepwise development plan in steps/ ([300f3b8](https://github.com/wtewalt/timeo/commit/300f3b8f2ca8964455c37285dee161af997a33cf))
|
|
37
|
+
* add timing-based progress estimation design to CLAUDE.md ([d6bcb9b](https://github.com/wtewalt/timeo/commit/d6bcb9bcd5c6ac891c55bd8a950b33720f5f8d84))
|
|
38
|
+
* document release-please, PyPI publishing, and pre-commit setup ([7d82fad](https://github.com/wtewalt/timeo/commit/7d82fad72d1682cf1aab0bfd54d14d001906e507))
|
|
39
|
+
* redesign README with badges, tables, and improved formatting ([bdb3f3f](https://github.com/wtewalt/timeo/commit/bdb3f3f22b644847c7423d68a6218276d01600be))
|
|
40
|
+
* resolve all open design questions in CLAUDE.md ([7acf12a](https://github.com/wtewalt/timeo/commit/7acf12a0861a29cb54f23cce5196b1ba4c7ec033))
|
|
41
|
+
* write README with usage examples for basic, learn, and concurrent modes ([87201b5](https://github.com/wtewalt/timeo/commit/87201b5abfa65d905e9b21f426df99a6ff38e35d))
|
|
42
|
+
|
|
43
|
+
## 0.1.0 (2026-04-03)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
### Features
|
|
47
|
+
|
|
48
|
+
* added rich as a dependency ([262c826](https://github.com/wtewalt/timeo/commit/262c826123b46339d83457578962f8a3e08e2cf1))
|
|
49
|
+
* change to release config ([094f8ea](https://github.com/wtewalt/timeo/commit/094f8ea74b23f885fdfb4e9dd0b6ea10a53d6cdd))
|
|
50
|
+
* enabled pre-commit checks and release tracking ([4ab1608](https://github.com/wtewalt/timeo/commit/4ab160830194534141c411777242a81f188294eb))
|
|
51
|
+
* flake added ([826aac3](https://github.com/wtewalt/timeo/commit/826aac3f67c76fbff8bb85fb1db9f570db22929d))
|
|
52
|
+
* new flake ([0f0b1f9](https://github.com/wtewalt/timeo/commit/0f0b1f93b4da0cb7c1d8a0eef50ef48175756de2))
|
|
53
|
+
* new nix config ([858f3ff](https://github.com/wtewalt/timeo/commit/858f3ff2d2af71f401c9b769ff871487e3f37042))
|
|
54
|
+
* updates to release ([1690435](https://github.com/wtewalt/timeo/commit/1690435928b06eeff239a49b448a80aaed3b580f))
|
|
55
|
+
* working flake with uv and pre-commit hooks ([c49b388](https://github.com/wtewalt/timeo/commit/c49b3888c56d8386b07445c39aa1b849e2abaaad))
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
### Bug Fixes
|
|
59
|
+
|
|
60
|
+
* added changelog ([59d9c6f](https://github.com/wtewalt/timeo/commit/59d9c6f138d6bd8299a8e8cf87b9d3b1f6a87d54))
|
|
61
|
+
* updates to allow release tracking ([76fa10f](https://github.com/wtewalt/timeo/commit/76fa10f46d56eb37b5b821edce96946b4b3e3dda))
|
timeo-0.2.0/CLAUDE.md
ADDED
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
# Timeo — Project Guide
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
**Timeo** is a Python package that lets developers annotate functions with a decorator to automatically track and display their progress in the terminal. When a script runs, all decorated functions are rendered together as a collection of live progress bars using `rich`.
|
|
6
|
+
|
|
7
|
+
## Core Concept
|
|
8
|
+
|
|
9
|
+
The user imports `timeo`, applies `@timeo.track` (or similar) to any function, and when the script executes, those functions are monitored and displayed as live progress bars in the terminal — all managed automatically without the user needing to manually wire up progress bar logic.
|
|
10
|
+
|
|
11
|
+
### Example Usage
|
|
12
|
+
|
|
13
|
+
```python
|
|
14
|
+
import timeo
|
|
15
|
+
|
|
16
|
+
@timeo.track
|
|
17
|
+
def process_files(files):
|
|
18
|
+
for file in files:
|
|
19
|
+
handle(file)
|
|
20
|
+
timeo.advance() # or automatic via iteration hooks
|
|
21
|
+
|
|
22
|
+
@timeo.track
|
|
23
|
+
def download_data(urls):
|
|
24
|
+
for url in urls:
|
|
25
|
+
fetch(url)
|
|
26
|
+
timeo.advance()
|
|
27
|
+
|
|
28
|
+
process_files(my_files)
|
|
29
|
+
download_data(my_urls)
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Terminal output (while running):
|
|
33
|
+
```
|
|
34
|
+
Processing files ━━━━━━━━━━━━━━━━━━━━━━ 45% 0:00:12
|
|
35
|
+
Downloading data ━━━━━━━━━━━━━━━━━━━━━━━━━━ 72% 0:00:04
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Architecture
|
|
39
|
+
|
|
40
|
+
### Key Components
|
|
41
|
+
|
|
42
|
+
- **`timeo/decorator.py`** — The `@timeo.track` decorator. Wraps a function, registers it with the progress manager, and starts/stops tracking around the function's execution.
|
|
43
|
+
- **`timeo/manager.py`** — The `ProgressManager` singleton. Owns the `rich.progress.Progress` instance, maintains the registry of tracked tasks, and controls the live display lifecycle.
|
|
44
|
+
- **`timeo/task.py`** — Represents a single tracked task (name, total, current progress, metadata).
|
|
45
|
+
- **`timeo/__init__.py`** — Public API surface. Exports `track`, `advance`, and any other user-facing symbols.
|
|
46
|
+
|
|
47
|
+
### Design Principles
|
|
48
|
+
|
|
49
|
+
- **Minimal user friction** — the decorator should be the only required touchpoint.
|
|
50
|
+
- **Non-intrusive** — does not require users to change the internals of their functions beyond adding `timeo.advance()` calls (or wrapping iterables).
|
|
51
|
+
- **Composable** — multiple decorated functions should render together as a unified live display, not separate progress bars.
|
|
52
|
+
- **Graceful degradation** — if `total` is unknown, show a spinner or indeterminate bar rather than failing.
|
|
53
|
+
|
|
54
|
+
## Dependencies
|
|
55
|
+
|
|
56
|
+
- **`rich`** — All progress bar rendering, formatting, and live display. Use `rich.progress.Progress` with `rich.live.Live` for the unified multi-bar display.
|
|
57
|
+
|
|
58
|
+
## Releasing
|
|
59
|
+
|
|
60
|
+
### Release Process
|
|
61
|
+
|
|
62
|
+
This repo uses **[release-please](https://github.com/googleapis/release-please)** to automate versioning and changelog generation. It parses conventional commits on `main` to determine the next version and opens a release PR automatically.
|
|
63
|
+
|
|
64
|
+
- Merge the release-please PR on `main` to cut a new release.
|
|
65
|
+
- On release, a GitHub Actions workflow automatically builds and publishes the package to **PyPI**.
|
|
66
|
+
- The version in `pyproject.toml` is the source of truth and is updated by release-please.
|
|
67
|
+
|
|
68
|
+
### Conventional Commits
|
|
69
|
+
|
|
70
|
+
All commits must follow the [Conventional Commits](https://www.conventionalcommits.org/) spec since release-please relies on them to determine version bumps:
|
|
71
|
+
|
|
72
|
+
| Prefix | Effect |
|
|
73
|
+
|--------|--------|
|
|
74
|
+
| `fix:` | Patch bump |
|
|
75
|
+
| `feat:` | Minor bump |
|
|
76
|
+
| `feat!:` / `BREAKING CHANGE:` | Major bump |
|
|
77
|
+
| `docs:`, `chore:`, `refactor:`, etc. | No version bump |
|
|
78
|
+
|
|
79
|
+
## Development
|
|
80
|
+
|
|
81
|
+
### Setup
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
uv sync # install dependencies
|
|
85
|
+
task dev # or however the dev environment is activated
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Code Quality
|
|
89
|
+
|
|
90
|
+
This repo uses **[pre-commit](https://pre-commit.com/)** to enforce code quality on every commit. Hooks are defined in `.pre-commit-config.yaml`.
|
|
91
|
+
|
|
92
|
+
Install hooks locally (one-time):
|
|
93
|
+
```bash
|
|
94
|
+
pre-commit install
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Hooks enforced:
|
|
98
|
+
- **ruff** — linting and formatting
|
|
99
|
+
- **mypy** — static type checking
|
|
100
|
+
- TOML/YAML validation
|
|
101
|
+
|
|
102
|
+
Run manually against all files:
|
|
103
|
+
```bash
|
|
104
|
+
pre-commit run --all-files
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Project Structure
|
|
108
|
+
|
|
109
|
+
```
|
|
110
|
+
timeo/
|
|
111
|
+
├── __init__.py # public API
|
|
112
|
+
├── decorator.py # @timeo.track implementation
|
|
113
|
+
├── manager.py # ProgressManager, rich.Progress integration
|
|
114
|
+
└── task.py # TrackedTask dataclass/model
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## Timing-Based Progress Estimation
|
|
118
|
+
|
|
119
|
+
### Overview
|
|
120
|
+
|
|
121
|
+
An opt-in mode (`@timeo.track(learn=True)`) that drives the progress bar using elapsed time against an expected duration gleaned from previous runs of that function. Rather than tracking discrete steps, the bar fills as `elapsed / expected_duration`.
|
|
122
|
+
|
|
123
|
+
### Opt-In Behavior
|
|
124
|
+
|
|
125
|
+
- Default behavior is **unchanged** — `@timeo.track` with no arguments works as always (step-based or indeterminate).
|
|
126
|
+
- Time-based estimation is activated explicitly: `@timeo.track(learn=True)`.
|
|
127
|
+
- On the **first run** (no cached data yet), display an indeterminate progress bar with a "Learning timing..." label so the user knows data is being collected but no estimate is available yet.
|
|
128
|
+
- On subsequent runs, use the cached EMA estimate to render a determinate time-driven progress bar.
|
|
129
|
+
|
|
130
|
+
### Local Timing Cache
|
|
131
|
+
|
|
132
|
+
- Stored at `~/.cache/timeo/timings.json` (use `platformdirs` for cross-platform path resolution).
|
|
133
|
+
- Each entry is keyed by a **hash of the function's bytecode** (`dis` or `inspect` + `hashlib`) rather than its name or module path. This ensures the cache automatically invalidates when the function's implementation changes — a refactored or updated function is treated as a new function with no prior data.
|
|
134
|
+
- Cache entry schema:
|
|
135
|
+
```json
|
|
136
|
+
{
|
|
137
|
+
"<fn_hash>": {
|
|
138
|
+
"name": "my_module.process_files",
|
|
139
|
+
"ema_duration_seconds": 12.4,
|
|
140
|
+
"run_count": 7,
|
|
141
|
+
"last_updated": "2026-04-08T00:00:00Z"
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
```
|
|
145
|
+
- `name` is stored for human readability/debugging only — it is never used as a lookup key.
|
|
146
|
+
|
|
147
|
+
### EMA Strategy
|
|
148
|
+
|
|
149
|
+
- After each run completes, update the stored estimate using an **Exponential Moving Average**:
|
|
150
|
+
```
|
|
151
|
+
ema = alpha * actual_duration + (1 - alpha) * previous_ema
|
|
152
|
+
```
|
|
153
|
+
- Suggested default `alpha = 0.2` (weights recent runs moderately; can be tuned).
|
|
154
|
+
- On the very first run, `ema` is seeded with the actual duration directly (no prior value to blend with).
|
|
155
|
+
- The EMA converges quickly enough that estimates become useful within 3–5 runs.
|
|
156
|
+
|
|
157
|
+
### Progress Bar Behavior
|
|
158
|
+
|
|
159
|
+
- The bar advances by `elapsed_time / ema_duration`, updated on a tick interval.
|
|
160
|
+
- If the function **overruns** the estimate, the bar stalls at ~99% rather than exceeding 100% or erroring — it completes to 100% only when the function actually returns.
|
|
161
|
+
- `rich` custom progress columns will be used to render this time-driven bar alongside any step-based bars in the same live display.
|
|
162
|
+
|
|
163
|
+
### Function Change Detection
|
|
164
|
+
|
|
165
|
+
- At call time, hash the function's bytecode (`fn.__code__.co_code` or the full `code` object via `marshal`/`hashlib`).
|
|
166
|
+
- If the hash does not match any cached entry, treat it as a brand-new function (show "Learning timing..." and start fresh).
|
|
167
|
+
- Old/stale entries for the previous hash are not automatically deleted — they accumulate silently. A future cache cleanup utility can address this.
|
|
168
|
+
|
|
169
|
+
## Design Decisions
|
|
170
|
+
|
|
171
|
+
### `total` — Inferred from `Sized` args
|
|
172
|
+
`timeo` inspects the arguments passed to a decorated function at call time and looks for any that implement `__len__()`. The first `Sized` argument found is used as `total`. No user input required. If no `Sized` argument is found, the bar is indeterminate.
|
|
173
|
+
|
|
174
|
+
### `advance()` — `contextvars.ContextVar` (under the hood)
|
|
175
|
+
`timeo.advance()` uses a `ContextVar` internally to know which task to update. The decorator pushes the current task onto the `ContextVar` before the function runs and pops it when it returns. This is fully transparent to the user — they only call `timeo.advance()`, no context management required. This also makes concurrent functions safe: threads and async tasks each see their own isolated `ContextVar` value.
|
|
176
|
+
|
|
177
|
+
### Iteration wrapping — `timeo.iter()`
|
|
178
|
+
`timeo.iter(items)` is supported as a convenience wrapper that automatically calls `advance()` on each iteration, eliminating the need for manual `advance()` calls:
|
|
179
|
+
|
|
180
|
+
```python
|
|
181
|
+
@timeo.track
|
|
182
|
+
def process_files(files):
|
|
183
|
+
for f in timeo.iter(files):
|
|
184
|
+
handle(f)
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
### Concurrent functions — Stacked bars with completed tasks collapsed
|
|
188
|
+
Multiple simultaneously-running tracked functions are each given their own row in the live display. When a function completes, its bar collapses to a single summary line with a checkmark and elapsed time. Only in-progress bars are shown in full:
|
|
189
|
+
|
|
190
|
+
```
|
|
191
|
+
✓ process_files 12.4s
|
|
192
|
+
download_data ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 72% 0:00:04
|
|
193
|
+
compress_output ━━━━━━━━━━ 20% 0:00:31
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
### Display lifecycle — Hybrid (automatic with optional explicit control)
|
|
197
|
+
By default, the live display starts automatically on the first decorated function call and tears down when the last tracked function finishes. Teardown is guaranteed via `try/finally` in the decorator so exceptions never leave the terminal in a broken state. A reference counter tracks how many tasks are active; the display is torn down when it reaches zero.
|
|
198
|
+
|
|
199
|
+
For complex scripts where automatic teardown is insufficient (e.g., conditional branching, multiprocessing), the user can take explicit control with a context manager:
|
|
200
|
+
|
|
201
|
+
```python
|
|
202
|
+
with timeo.live():
|
|
203
|
+
process_files(my_files)
|
|
204
|
+
download_data(my_urls)
|
|
205
|
+
# display always tears down cleanly here
|
|
206
|
+
```
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
# Conventions
|
|
2
|
+
|
|
3
|
+
Best practices and rules for making changes to this repository.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Development Environment
|
|
8
|
+
|
|
9
|
+
This repo uses [Nix flakes](https://nixos.wiki/wiki/Flakes) to provide a fully reproducible development environment. **All development and testing must be done inside the Nix shell.** Tools like `uv`, `python`, `task`, and `pre-commit` are provided by the Nix environment and may not be available outside of it.
|
|
10
|
+
|
|
11
|
+
### Entering the shell
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
nix develop
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
This drops you into a shell with all dependencies and tools available. Run this before doing anything else in the repo.
|
|
18
|
+
|
|
19
|
+
### Why
|
|
20
|
+
Running commands outside the Nix shell (e.g., using a system Python or a globally installed `uv`) risks version mismatches and unreproducible behaviour. Always use the Nix shell.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Commits
|
|
25
|
+
|
|
26
|
+
All commits must follow the [Conventional Commits](https://www.conventionalcommits.org/) spec. This is required — release-please parses commit messages on `main` to determine version bumps and generate the changelog automatically.
|
|
27
|
+
|
|
28
|
+
### Format
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
<type>[optional scope]: <short description>
|
|
32
|
+
|
|
33
|
+
[optional body]
|
|
34
|
+
|
|
35
|
+
[optional footer(s)]
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Types and version impact
|
|
39
|
+
|
|
40
|
+
| Type | When to use | Version bump |
|
|
41
|
+
|---|---|---|
|
|
42
|
+
| `feat` | A new user-facing feature | Minor (`0.x.0`) |
|
|
43
|
+
| `fix` | A bug fix | Patch (`0.0.x`) |
|
|
44
|
+
| `feat!` / `fix!` | Breaking change (also add `BREAKING CHANGE:` footer) | Major (`x.0.0`) |
|
|
45
|
+
| `refactor` | Code restructuring with no behaviour change | None |
|
|
46
|
+
| `docs` | Documentation only | None |
|
|
47
|
+
| `chore` | Maintenance, dependency updates, config | None |
|
|
48
|
+
| `test` | Adding or updating tests | None |
|
|
49
|
+
| `ci` | CI/CD workflow changes | None |
|
|
50
|
+
| `style` | Formatting, whitespace (no logic change) | None |
|
|
51
|
+
| `perf` | Performance improvement | None |
|
|
52
|
+
|
|
53
|
+
### Examples
|
|
54
|
+
|
|
55
|
+
```
|
|
56
|
+
feat: add timeo.iter() convenience wrapper
|
|
57
|
+
fix: reset ContextVar correctly on exception in track decorator
|
|
58
|
+
feat!: rename advance() to step()
|
|
59
|
+
|
|
60
|
+
BREAKING CHANGE: advance() has been renamed to step() for clarity
|
|
61
|
+
docs: add learn mode usage examples to README
|
|
62
|
+
chore: update rich to 14.4.0
|
|
63
|
+
test: add cache EMA update unit tests
|
|
64
|
+
ci: add Python 3.13 to test matrix
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Rules
|
|
68
|
+
- Use the **imperative mood** in the description ("add", "fix", "update" — not "added", "fixes", "updating").
|
|
69
|
+
- Keep the description under **72 characters**.
|
|
70
|
+
- Do not end the description with a period.
|
|
71
|
+
- If a commit addresses a GitHub issue, reference it in the footer: `Closes #42`.
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## Branches
|
|
76
|
+
|
|
77
|
+
| Branch | Purpose |
|
|
78
|
+
|---|---|
|
|
79
|
+
| `main` | Production-ready code only. release-please runs here. |
|
|
80
|
+
| `dev` | Active development branch. All work happens here or in feature branches off `dev`. |
|
|
81
|
+
| `feat/<name>` | Optional feature branches for larger pieces of work, branched from `dev`. |
|
|
82
|
+
|
|
83
|
+
- Never commit directly to `main` — always go through a PR.
|
|
84
|
+
- PRs from `dev` → `main` should only be made when the code is ready to release.
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## Pre-commit
|
|
89
|
+
|
|
90
|
+
This repo uses [pre-commit](https://pre-commit.com/) to enforce code quality automatically on every commit. Hooks are defined in `.pre-commit-config.yaml`.
|
|
91
|
+
|
|
92
|
+
### Setup (one-time)
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
pre-commit install
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
This installs the hooks into `.git/hooks/` so they run automatically on `git commit`.
|
|
99
|
+
|
|
100
|
+
### Hooks that run on every commit
|
|
101
|
+
|
|
102
|
+
| Hook | What it checks |
|
|
103
|
+
|---|---|
|
|
104
|
+
| `check-yaml` | YAML files are valid |
|
|
105
|
+
| `end-of-file-fixer` | All files end with a newline |
|
|
106
|
+
| `trailing-whitespace` | No trailing whitespace on any line |
|
|
107
|
+
| `black` | Python code is formatted with Black |
|
|
108
|
+
|
|
109
|
+
### Running manually
|
|
110
|
+
|
|
111
|
+
To run all hooks against all files (useful before opening a PR):
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
pre-commit run --all-files
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
To run a specific hook:
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
pre-commit run black --all-files
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### Rules
|
|
124
|
+
- Never bypass hooks with `--no-verify` unless there is a documented, exceptional reason.
|
|
125
|
+
- If a hook fails, fix the issue and re-stage before committing — do **not** amend a previous commit to work around it.
|
|
126
|
+
- The CI pipeline also runs `pre-commit run --all-files` — a passing local run is required before pushing.
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
## Code Style
|
|
131
|
+
|
|
132
|
+
- **Formatter:** [Black](https://black.readthedocs.io/) (enforced by pre-commit).
|
|
133
|
+
- **Line length:** Black's default (88 characters).
|
|
134
|
+
- **Type annotations:** Required on all public functions and methods. mypy must pass with no errors.
|
|
135
|
+
- **Docstrings:** Use for all public modules, classes, and functions. Keep them concise.
|
|
136
|
+
- **Imports:** Standard library first, third-party second, local last — separated by blank lines.
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
## Testing
|
|
141
|
+
|
|
142
|
+
- All new features must be accompanied by tests in `tests/`.
|
|
143
|
+
- Tests live in `tests/` at the repo root and mirror the `timeo/` module structure (e.g., `timeo/cache.py` → `tests/test_cache.py`).
|
|
144
|
+
- Use `pytest`. Run with:
|
|
145
|
+
```bash
|
|
146
|
+
uv run pytest
|
|
147
|
+
```
|
|
148
|
+
- Do not write tests that render to the terminal or write to `~/.cache/timeo/` — mock `ProgressManager` and patch `timeo.cache._cache_path` in tests that touch the cache.
|
|
149
|
+
- Aim for >80% coverage on all modules.
|
|
150
|
+
|
|
151
|
+
---
|
|
152
|
+
|
|
153
|
+
## Releasing
|
|
154
|
+
|
|
155
|
+
Releases are fully automated — do not manually edit version numbers or create GitHub releases by hand.
|
|
156
|
+
|
|
157
|
+
1. Merge commits following the Conventional Commits spec into `main`.
|
|
158
|
+
2. release-please automatically opens a release PR updating `pyproject.toml` and `CHANGELOG.md`.
|
|
159
|
+
3. Merge the release PR → a GitHub release is created → the publish workflow pushes the new version to PyPI.
|
|
160
|
+
|
|
161
|
+
See `CLAUDE.md` for full details on the release process.
|
|
162
|
+
|
|
163
|
+
---
|
|
164
|
+
|
|
165
|
+
## Dependencies
|
|
166
|
+
|
|
167
|
+
- Manage dependencies with `uv`. Never edit `uv.lock` by hand.
|
|
168
|
+
- To add a dependency: `uv add <package>`
|
|
169
|
+
- To add a dev dependency: `uv add --dev <package>`
|
|
170
|
+
- Keep `uv.lock` committed and up to date.
|
|
171
|
+
- Pin dependency versions loosely in `pyproject.toml` (e.g., `rich>=14.0`) and let `uv.lock` handle exact pinning.
|
timeo-0.2.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 WilliamTewalt
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|