klogr 0.1.3__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.
@@ -0,0 +1,41 @@
1
+ ---
2
+ name: Publish to PyPI
3
+
4
+ on:
5
+ push:
6
+ tags:
7
+ - v*
8
+
9
+ jobs:
10
+ build-and-publish:
11
+ runs-on: ubuntu-latest
12
+
13
+ steps:
14
+ - name: Check out the code
15
+ uses: actions/checkout@v4
16
+
17
+ - name: Set up Python
18
+ uses: actions/setup-python@v5
19
+ with:
20
+ python-version: "3.12"
21
+
22
+ - name: Install dependencies
23
+ run: |
24
+ python -m pip install --upgrade pip setuptools wheel build twine
25
+
26
+ - name: Build the package
27
+ run: python -m build
28
+
29
+ - name: Show wheel metadata (debug)
30
+ run: |
31
+ python -m twine check dist/*
32
+ for f in dist/*.whl; do
33
+ echo "===== $f ====="
34
+ python -c "import zipfile, sys; zf = zipfile.ZipFile(sys.argv[1]); [print(zf.read(n).decode()) for n in zf.namelist() if n.endswith('METADATA')]" "$f"
35
+ done
36
+
37
+ - name: Publish to PyPI
38
+ env:
39
+ TWINE_USERNAME: __token__
40
+ TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
41
+ run: python -m twine upload --verbose --non-interactive dist/*
klogr-0.1.3/.gitignore ADDED
@@ -0,0 +1,30 @@
1
+
2
+ # Pretrained Torch
3
+ pretrained_models/
4
+
5
+ # PyCache
6
+ __pycache__
7
+
8
+ # VSCode
9
+ *.code-workspace
10
+
11
+ # Small Data/Videos
12
+ small_data/
13
+ data/
14
+ *.zip
15
+
16
+ # Outputs
17
+ outputs/
18
+ logs/
19
+ log/
20
+ .DS_Store
21
+
22
+ # builds
23
+ build/
24
+ dist/
25
+ .venv/
26
+ *.tar.gz
27
+ *.tar
28
+
29
+ # Lock file — managed by uv, regenerated per-environment
30
+ uv.lock
@@ -0,0 +1,235 @@
1
+ ---
2
+ # $ pre-commit install --install-hooks
3
+ # See https://pre-commit.com for more information
4
+ # See https://pre-commit.com/hooks.html for more hooks
5
+ default_install_hook_types:
6
+ - pre-commit
7
+ - post-checkout
8
+ - post-merge
9
+ - post-rewrite
10
+ repos:
11
+ - repo: https://github.com/pre-commit/pre-commit-hooks
12
+ rev: v5.0.0
13
+ hooks:
14
+ - id: trailing-whitespace
15
+
16
+ - id: end-of-file-fixer
17
+
18
+ - id: mixed-line-ending
19
+ args: [--fix, lf]
20
+
21
+ - id: check-yaml
22
+ exclude: .pre-commit-config.yaml
23
+
24
+ - id: check-added-large-files
25
+ args: [--maxkb=2000]
26
+
27
+ - id: check-merge-conflict
28
+ - id: check-case-conflict
29
+ - id: check-json
30
+ - id: check-toml
31
+ exclude: uv\.lock
32
+ - id: pretty-format-json
33
+ args: [--autofix, --no-ensure-ascii, --no-sort-keys]
34
+
35
+ # - id: double-quote-string-fixer
36
+ # exclude: ^dependencies/|^experiments/dependencies/|^experiments/
37
+
38
+ # - repo: https://github.com/psf/black-pre-commit-mirror
39
+ # rev: 24.10.0
40
+ # hooks:
41
+ # - id: black
42
+ # # It is recommended to specify the latest version of Python
43
+ # # supported by your project here, or alternatively use
44
+ # # pre-commit's default_language_version, see
45
+ # # https://pre-commit.com/#top_level-default_language_version
46
+ # language_version: python3.10
47
+ # exclude: ^dependencies/|^experiments/dependencies/|^experiments/|uv\.lock|tools/generative/pipelines/|configs/hax_ml/|unittest_logs/
48
+ # args: [--line-length=79, --target-version=py310, --quiet]
49
+
50
+ # - repo: https://github.com/PyCQA/docformatter # there is an issue with the latest version and python_env - uncomment as soon as possible
51
+ # rev: v1.3.1
52
+ # hooks:
53
+ # - id: docformatter
54
+ # language: python
55
+ # exclude: ^dependencies/|^experiments/dependencies/|^experiments/|uv\.lock|tools/generative/pipelines/|configs/hax_ml/|unittest_logs/
56
+ # args: [--in-place, --wrap-descriptions, '120']
57
+
58
+ - repo: https://github.com/jumanjihouse/pre-commit-hook-yamlfmt
59
+ rev: 0.2.3 # or other specific tag
60
+ hooks:
61
+ - id: yamlfmt
62
+ exclude: .pre-commit-config.yaml
63
+
64
+
65
+ # - repo: https://github.com/asottile/pyupgrade
66
+ # rev: v3.3.1
67
+ # hooks:
68
+ # - id: pyupgrade
69
+ # args:
70
+ # # - --py36-plus
71
+ # # - --py37-plus
72
+ # # - --py38-plus
73
+ # - --py39-plus
74
+ # # # - --py311-plus
75
+ # exclude: ^dependencies/|^dependencies/|^experiments/dependencies/|^experiments/
76
+
77
+ - repo: https://github.com/lovesegfault/beautysh
78
+ rev: v6.2.1
79
+ hooks:
80
+ - id: beautysh
81
+ exclude: uv\.lock
82
+
83
+
84
+
85
+ # - repo: https://github.com/pycqa/isort
86
+ # rev: 5.11.2
87
+ # hooks:
88
+ # - id: isort
89
+ # name: isort
90
+ # entry: isort
91
+ # language: python
92
+ # types: [python]
93
+ # exclude: ^^dependencies/|^experiments/|^cli/
94
+ # args: [--line-length=89, --multi-line=3, --force-grid-wrap=0, --trailing-comma, --use-parentheses, --ensure-newline-before-comments]
95
+ # # additional_dependencies: [isort>=5.12.0]
96
+
97
+ - repo: local
98
+ hooks:
99
+ - id: toml-sort-fix
100
+ name: toml-sort-fix
101
+ entry: toml-sort
102
+ args: [--in-place]
103
+ language: python
104
+ exclude: uv\.lock
105
+ types: [toml]
106
+ additional_dependencies: [toml-sort>=0.23.1]
107
+
108
+ - id: toml-sort
109
+ name: toml-sort
110
+ entry: toml-sort
111
+ args: [--check]
112
+ language: python
113
+ exclude: uv\.lock
114
+ types: [toml]
115
+ additional_dependencies: [toml-sort>=0.23.1]
116
+
117
+ - repo: https://github.com/pycqa/pylint
118
+ rev: v3.3.7
119
+ hooks:
120
+ - id: pylint
121
+ name: pylint
122
+ entry: pylint
123
+ language: system
124
+ types: [python]
125
+ exclude: uv\.lock
126
+ args: [
127
+ --disable=all, # disable all rules
128
+ --enable=E0401, # import-error
129
+ --enable=E1123, # unexpected-keyword-arg
130
+ --enable=E1125, # missing-kwoa
131
+ --enable=E1101, # no-member
132
+ ]
133
+ stages: [manual]
134
+ # ruff is trying to include more of this: https://github.com/astral-sh/ruff/issues/970
135
+
136
+ - repo: https://github.com/astral-sh/ruff-pre-commit
137
+ # Ruff version.
138
+ rev: v0.12.7
139
+ hooks:
140
+ # Run the linter.
141
+ - id: ruff
142
+ args: [
143
+ --fix,
144
+ --unsafe-fixes,
145
+ --select,
146
+ ALL,
147
+ --ignore, C901, # class too complex # TODO: remove this at some point
148
+ --ignore, D203, # 1 blank line required before class docstring
149
+ --ignore, D211, # No blank lines allowed before class docstring
150
+ --ignore, D213, # Multi-line docstring summary should start at the second line
151
+ --ignore, F722, # jaxtyping annotations
152
+ --ignore, F821, # jaxtyping annotations with the name in strings
153
+ --ignore, E501, # Line too long ( > 79 characters)
154
+ --ignore, T201, # Checks for print statements and remove them
155
+ --ignore, COM812, # Missing trailing comma in a dictionary
156
+ --ignore, ISC001, # Checks for implicitly concatenated strings on a single line.
157
+ --ignore, G004, # Logging statement uses f-string
158
+ --ignore, ERA001, # Found commented-out code
159
+ --ignore, S607, # Starting a process with a partial executable path
160
+ --ignore, S603, # `subprocess` call: check for execution of untrusted input
161
+ --ignore, ANN401, # Dynamically typed expressions (typing.Any) are disallowed in `**kwargs`
162
+ --ignore, RUF012, # Mutable class attributes should be annotated with `typing.ClassVar
163
+ --ignore, PERF401, # Use a list comprehension to create a transformed list
164
+ --ignore, SIM115, # Use a context manager for opening files
165
+ --ignore, PERF203, # `try`-`except` within a loop incurs performance overhead
166
+ --ignore, PLW2901, # `for` loop variable overwritten by assignment target
167
+ --ignore, TCH010, # Invalid string member in `X | Y`-style union type
168
+ --ignore, N812, # Lowercase `functional` imported as non-lowercase `F`
169
+ --ignore, SLF001, # Private member accessed
170
+ --ignore, TRY301, # Abstract `raise` to an inner function
171
+ --ignore, NPY002, # Replace legacy `np.random.random` call with `np.random.Generator`
172
+ --ignore, TD003, # Missing issue link on the line following a TODO
173
+ --ignore, FIX002, # Line contains TODO, consider resolving the issue
174
+ --ignore, S311, # Standard pseudo-random generators are not suitable for cryptographic purposes
175
+ --ignore, TC006, # Add quotes to type expression in `typing.cast()`
176
+ --ignore, TRY401, # Redundant exception object included in `logging.exception` call
177
+ --ignore, BLE001, # Do not catch blind exception: `Exception`'
178
+ --ignore, PLR0911, # Too many return statements
179
+ --ignore, PLR0912, # Too many branches
180
+ --ignore, PLR0913, # Too many arguments in function definition
181
+ --ignore, PLR0915, # Too many statements
182
+ --ignore, PLR2004, # Magic value used in comparison, consider replacing `0.1` with a constant variable
183
+ --ignore, PLC0415, # `import` should be at the top-level of a file
184
+ --ignore, PT028, # Test function parameter `<>` has default argument
185
+ ]
186
+ exclude: uv\.lock
187
+ # Run the formatter.
188
+ - id: ruff-format
189
+ exclude: uv\.lock
190
+ # - repo: https://github.com/jvllmr/poetry-types
191
+ # rev: v0.4.0
192
+ # hooks:
193
+ # - id: poetry-types
194
+
195
+ - repo: https://github.com/pre-commit/mirrors-mypy
196
+ rev: v1.18.2
197
+ hooks:
198
+ - id: mypy
199
+ name: mypy
200
+ entry: mypy
201
+ language: python
202
+ types_or: [python, pyi]
203
+ exclude: uv\.lock
204
+ require_serial: true
205
+ # args: [--strict, --ignore-missing-imports]
206
+ args: [--ignore-missing-imports, --scripts-are-modules, --install-types, --non-interactive, --warn-unused-ignores, --show-error-codes, --check-untyped-defs,
207
+ --disallow-incomplete-defs, --explicit-package-bases, --warn-redundant-casts, --strict-equality, --strict-equality-for-none, --follow-untyped-imports]
208
+ additional_dependencies:
209
+ - pydantic
210
+ - types-requests
211
+ # mypy --install-types
212
+
213
+
214
+ - repo: https://github.com/astral-sh/uv-pre-commit
215
+ # uv version.
216
+ rev: 0.8.3
217
+ hooks:
218
+ - id: uv-sync
219
+ stages: [pre-commit, pre-merge-commit, pre-push] # not run during manual. manual is run in Github, and this one is expensive
220
+ - id: uv-export
221
+ args: [--frozen, --output-file=requirements.txt, --no-hashes]
222
+ stages: [pre-commit, pre-merge-commit, pre-push]
223
+
224
+
225
+ # - repo: https://github.com/oxsecurity/megalinter # sudo apt install npm && sudo npm install mega-linter-runner -g
226
+ # # install docker https://docs.docker.com/engine/install/ubuntu/#set-up-the-repository
227
+ # # install with sudo once: sudo mega-linter-runner .
228
+ # rev: v6.8.0 # Git tag specifying the hook, not mega-linter-runner, version
229
+ # hooks:
230
+ # - id: megalinter-incremental # Faster, less thorough
231
+ # stages:
232
+ # - commit
233
+ # - id: megalinter-full # Slower, more thorough
234
+ # stages:
235
+ # - push
@@ -0,0 +1,113 @@
1
+ # Changelog
2
+
3
+ All notable changes to klogr are documented here.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/)
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [0.1.3] — 2026-05-17
9
+
10
+ PyPI rejected the name `klog` with `400 The name 'klog' isn't allowed.`
11
+ (likely too close to existing Linux/Go-ecosystem names). Renamed to
12
+ `klogr` — same project, one letter different.
13
+
14
+ ### Changed
15
+
16
+ - Package renamed `klog` → `klogr`. GitHub repo also renamed from
17
+ `affromero/klog` to `affromero/klogr`. Imports change accordingly:
18
+ `from klog import X` → `from klogr import X`.
19
+ - README install instructions, badges, and layout diagram updated for
20
+ the new name.
21
+
22
+ ### Fixed
23
+
24
+ - README incorrectly claimed `get_cache_dir()` returns `~/.cache/klog`;
25
+ it actually returns the XDG cache root directly. Updated the comment.
26
+
27
+ ## [0.1.2] — 2026-05-15
28
+
29
+ CI/workflow fixes after 0.1.1 also failed PyPI upload with the same
30
+ `400 Bad Request`. Local wheels build cleanly with the SPDX license
31
+ metadata — the failure is somewhere in the upload step. This release
32
+ adds `twine check` and `twine upload --verbose` to surface the actual
33
+ error.
34
+
35
+ ### Fixed
36
+
37
+ - `.github/workflows/publish.yml`: bump `actions/checkout` to v4,
38
+ `actions/setup-python` to v5, pin Python to 3.12 (3.x picked 3.14
39
+ which prints deprecation noise).
40
+ - Add `twine check` and METADATA dump steps so we can see what's in
41
+ the wheel before upload.
42
+ - `twine upload --verbose --non-interactive` for actionable errors
43
+ when the upload step itself fails.
44
+
45
+ ## [0.1.1] — 2026-05-15
46
+
47
+ First successful PyPI upload. Fixes the 0.1.0 build that PyPI rejected
48
+ with `400 Bad Request`.
49
+
50
+ ### Fixed
51
+
52
+ - `pyproject.toml` `license` field uses the SPDX expression
53
+ (`license = "MIT"` + `license-files = ["LICENSE.md"]`) instead of the
54
+ legacy `{text = "MIT"}` form that newer PyPI rejects.
55
+ - Drop redundant `License :: OSI Approved :: MIT License` classifier
56
+ (the SPDX expression replaces it; PyPI rejects both together).
57
+ - Project URLs in `pyproject.toml` corrected from `github.com/afromero/...`
58
+ to `github.com/affromero/...` (the actual GitHub handle).
59
+ - README ASCII diagram rewritten with ASCII-safe box characters
60
+ (`+--+`, `-->`) instead of Unicode box-drawing + `▶`. The previous
61
+ version rendered crooked in Markdown viewers that gave `▶` a wider
62
+ glyph than expected.
63
+
64
+ ## [0.1.0] — 2026-05-15
65
+
66
+ First release under the new name `klogr`. Previously published as `difflogtest`.
67
+
68
+ ### Changed
69
+
70
+ - **Renamed package** from `difflogtest` to `klogr`. The old name described a snapshot-test framework that is no longer part of this package.
71
+ - **Flattened the module layout.** The old `difflogtest.logging.*` and `difflogtest.utils.*` sub-packages have been merged into top-level modules:
72
+ - `difflogtest.logging.core` → `klogr.logger`
73
+ - `difflogtest.logging.cache_tools` → `klogr.cache`
74
+ - `difflogtest.utils.strings` → `klogr.time`
75
+ - `difflogtest.utils.path` → `klogr.path/` (split into `ops`, `io`, `query`, `env`)
76
+ - `logger.info_json` now uses stdlib `json` instead of `json5` — Pydantic-emitted JSON doesn't need comment tolerance.
77
+
78
+ ### Removed
79
+
80
+ - **Snapshot test framework** (`UnitTests`, `@register_unittest`, `LogReplacement`, `is_unittest_mode`, `run-unittests` CLI). Not maintained; rely on `pytest` directly.
81
+ - **`seed_everything`** — pixelcache ships its own; this one only existed for the snapshot framework.
82
+ - **`wait_seconds_bar`** — zero consumers.
83
+ - **`path_download_and_extract_tar`** — drops the `wget` dependency.
84
+ - **`is_file_changed`, `logfile_from_func`, `path_from_pattern`, `keep_local_data`** — snapshot-only path helpers.
85
+
86
+ ### Dropped dependencies
87
+
88
+ - `tyro` (snapshot CLI)
89
+ - `pytz` (unused)
90
+ - `json5` (info_json rewritten to stdlib json)
91
+ - `torch`, `torchvision` (seed_everything was the only consumer)
92
+ - `wget` (download helper removed)
93
+ - `GitPython` (`is_file_changed` was the only consumer)
94
+
95
+ ### Kept
96
+
97
+ - `get_logger`, `LoggingRich`, `LoggingTable`, `DEFAULT_VERBOSITY`
98
+ - `lru_cache`, `DisableableLRUCache`, `get_cache_dir`, `sha256sum`
99
+ - All path helpers consumed by Hax-CV (`path_join`, `path_exists`, `path_mkdir`, `path_open`, `path_glob`, `path_basename`, `path_dirname`, `path_stem`, `path_is_s3`, `path_absolute`, `path_abs`, `path_dotenv`, `path_home`, `path_expanduser`, `path_relative_to`, `path_resolve`, `path_replace_suffix`, `path_rstrip`, `path_write_text`, `path_read_text`, `path_listdir`, `path_remove`, `path_remove_dir`, `path_copy`, `path_copy_dir`, `path_move`, `path_rename`, `path_symlink`, `path_islink`, `path_lexists`, `path_is_dir`, `path_is_file`, `path_is_image_file`, `path_is_video_file`, `path_getmtime`, `path_stat`, `path_dir_empty`, `path_exists_and_not_empty`, `path_startswith`, `path_endswith`, `path_newest_dir`, `path_newest_file`, `path_rglob`, `path_s3_bucket_name`, `is_glob_pattern`, `expand_glob_to_temp_dir`)
100
+ - `get_elapsed_time`
101
+
102
+ ### Migration
103
+
104
+ Replace every `from difflogtest...` import:
105
+
106
+ | Old | New |
107
+ |-----|-----|
108
+ | `from difflogtest import get_logger` | `from klogr import get_logger` |
109
+ | `from difflogtest import lru_cache, sha256sum` | `from klogr import lru_cache, sha256sum` |
110
+ | `from difflogtest.utils.path import path_X` | `from klogr.path import path_X` |
111
+ | `from difflogtest.logging.core import LoggingTable` | `from klogr import LoggingTable` |
112
+
113
+ If you depended on `@register_unittest`, `is_unittest_mode`, or `LogReplacement` — those are gone. Use `pytest` directly.
klogr-0.1.3/PKG-INFO ADDED
@@ -0,0 +1,214 @@
1
+ Metadata-Version: 2.4
2
+ Name: klogr
3
+ Version: 0.1.3
4
+ Summary: Batteries-included structured logger for Python data/ML projects, built on Rich.
5
+ Project-URL: Changelog, https://github.com/affromero/klogr/blob/main/CHANGELOG.md
6
+ Project-URL: Homepage, https://github.com/affromero/klogr
7
+ Project-URL: Issues, https://github.com/affromero/klogr/issues
8
+ Author-email: Andres Romero <me@afromero.co>
9
+ License-Expression: MIT
10
+ Keywords: cache,logging,ml,path,rich,s3
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Environment :: Console
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
20
+ Classifier: Topic :: System :: Logging
21
+ Requires-Python: >=3.10
22
+ Requires-Dist: beartype>=0.21.0
23
+ Requires-Dist: jaxtyping>=0.3.2
24
+ Requires-Dist: natsort>=8.4.0
25
+ Requires-Dist: pydantic>=2.10.2
26
+ Requires-Dist: python-dotenv>=0.19.0
27
+ Requires-Dist: rich>=13.9.4
28
+ Requires-Dist: sha256sum>=2024.4.26
29
+ Description-Content-Type: text/markdown
30
+
31
+ <div align="center">
32
+
33
+ # klogr
34
+
35
+ **Batteries-included structured logger for Python data/ML projects — built on [Rich](https://github.com/Textualize/rich).**
36
+
37
+ [![PyPI](https://img.shields.io/pypi/v/klogr)](https://pypi.org/project/klogr/)
38
+ [![Downloads](https://img.shields.io/pypi/dm/klogr)](https://pypi.org/project/klogr/)
39
+ [![Python](https://img.shields.io/badge/python-3.10%2B-blue?logo=python&logoColor=white)](https://pypi.org/project/klogr/)
40
+ [![Publish](https://img.shields.io/github/actions/workflow/status/affromero/klogr/publish.yml?label=publish)](https://github.com/affromero/klogr/actions/workflows/publish.yml)
41
+ [![License: MIT](https://img.shields.io/github/license/affromero/klogr)](https://github.com/affromero/klogr/blob/main/LICENSE.md)
42
+ [![uv](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/uv/main/assets/badge/v0.json)](https://github.com/astral-sh/uv)
43
+ [![Ruff](https://img.shields.io/badge/code%20style-ruff-261230?logo=ruff)](https://github.com/astral-sh/ruff)
44
+ [![mypy](https://img.shields.io/badge/typing-mypy%20strict-blue)](http://mypy-lang.org/)
45
+ [![jaxtyping](https://img.shields.io/badge/shapes-jaxtyping-orange)](https://github.com/patrick-kidger/jaxtyping)
46
+ [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](https://github.com/affromero/klogr/pulls)
47
+
48
+ </div>
49
+
50
+ ```text
51
+ get_logger()
52
+ |
53
+ v
54
+ +---------------------------+
55
+ | LoggingRich |
56
+ +---------------------------+
57
+ | .info .success .warning | --> Rich-formatted console
58
+ | .error .debug .rule | with auto file:line prefix
59
+ | .track .print .info_json|
60
+ +---------------------------+
61
+
62
+ plus, from the same package:
63
+
64
+ @lru_cache + DisableableLRUCache --> cached calls,
65
+ with a disable hook for tests
66
+ path_join / path_exists / path_open --> local + s3:// transparent
67
+ get_elapsed_time(seconds) --> "01d : 01h : 12m : 03s"
68
+ sha256sum(path) / get_cache_dir() --> stable on-disk caching
69
+ ```
70
+
71
+ `klogr` is what you reach for when stdlib `logging.basicConfig` doesn't cut it and `print()` feels gross. It wraps Python's `logging` module with `rich.logging.RichHandler` pre-configured, adds stacklevel awareness so every line shows where it came from, and ships a handful of helpers (caching, paths, timing) that show up in every ML/data project.
72
+
73
+ ## Why use it
74
+
75
+ - **Drop-in `get_logger()`** — module-scoped, cached, zero setup. No `logging.basicConfig`, no handler wiring.
76
+ - **Stacklevel-aware** — every log line auto-prefixes with the caller's `file:line`. Stop hunting for which module shouted what.
77
+ - **`logger.track()`** — wraps `rich.progress.track()` and works inside `ThreadPoolExecutor` / `ProcessPoolExecutor` without going silent.
78
+ - **`logger.print()` / `logger.info_json()`** — pretty-print Pydantic models, dicts, JSON without piping through `print(json.dumps(..., indent=2))`.
79
+ - **`@lru_cache` with a disable hook** — flip an env var to bypass caching globally (handy for tests).
80
+ - **Path helpers that handle S3** — `path_join`, `path_exists`, `path_mkdir`, etc. work whether you pass `/tmp/foo` or `s3://bucket/foo`.
81
+ - **`get_elapsed_time(seconds)`** — formats float seconds as `00d : 01h : 12m : 03s`.
82
+
83
+ ## Install
84
+
85
+ ```bash
86
+ uv add klogr
87
+ # or
88
+ pip install klogr
89
+ ```
90
+
91
+ Requires Python ≥ 3.10. Runtime deps: `rich`, `pydantic`, `python-dotenv`, `beartype`, `jaxtyping`, `natsort`, `sha256sum`.
92
+
93
+ ## Quickstart
94
+
95
+ ```python
96
+ from klogr import get_logger
97
+
98
+ logger = get_logger()
99
+
100
+ logger.info("loaded %d samples", 1024) # auto file:line prefix
101
+ logger.success("training converged") # green check
102
+ logger.warning("validation loss plateaued")
103
+ logger.error("OOM on batch 42")
104
+
105
+ # Pretty-print structured data
106
+ logger.print({"epoch": 12, "lr": 3e-4, "loss": 0.21})
107
+
108
+ # Progress bar that works in parallel
109
+ from concurrent.futures import ThreadPoolExecutor
110
+ with ThreadPoolExecutor(max_workers=8) as pool:
111
+ for result in logger.track(pool.map(process, items), total=len(items), description="batch"):
112
+ ...
113
+ ```
114
+
115
+ ## What's in the box
116
+
117
+ ### Logger
118
+
119
+ ```python
120
+ from klogr import get_logger, LoggingRich, DEFAULT_VERBOSITY
121
+
122
+ logger = get_logger() # default verbosity
123
+ quiet = get_logger({"info": False, "debug": False}) # custom
124
+
125
+ logger.info(msg) # cyan
126
+ logger.success(msg) # green check
127
+ logger.warning(msg) # yellow
128
+ logger.error(msg) # red
129
+ logger.rule(title) # horizontal divider
130
+ logger.print(any_object) # rich.print, soft-wrap aware
131
+ logger.info_json(json_str) # syntax-colored JSON
132
+ logger.track(iter, total, description) # progress bar
133
+ ```
134
+
135
+ ### Caching
136
+
137
+ ```python
138
+ from klogr import lru_cache, DisableableLRUCache, get_cache_dir, sha256sum
139
+
140
+ @lru_cache(maxsize=128)
141
+ def expensive(key: str) -> bytes:
142
+ ...
143
+
144
+ # Stable on-disk paths
145
+ cache_root = get_cache_dir() # XDG cache root
146
+ digest = sha256sum("/path/to/file.bin") # hex string
147
+ ```
148
+
149
+ ### Path helpers (local + S3 transparent)
150
+
151
+ ```python
152
+ from klogr.path import (
153
+ path_join, path_exists, path_mkdir, path_dirname,
154
+ path_basename, path_glob, path_is_s3, path_open,
155
+ )
156
+
157
+ path_exists("/tmp/local.bin") # True/False
158
+ path_exists("s3://bucket/key.bin") # True/False (same call)
159
+ path_mkdir("/tmp/nested/dir", parents=True, exist_ok=True)
160
+ for p in path_glob("/data/*.jpg"):
161
+ with path_open(p, "rb") as f:
162
+ ...
163
+ ```
164
+
165
+ ### Timing
166
+
167
+ ```python
168
+ from klogr import get_elapsed_time
169
+
170
+ print(get_elapsed_time(86400 + 3661)) # '01d : 01h : 01m : 01s'
171
+ ```
172
+
173
+ ## Examples
174
+
175
+ Runnable scripts live in [`examples/`](examples/):
176
+
177
+ - [`01_basic_logger.py`](examples/01_basic_logger.py) — log levels, formatting, file:line prefix
178
+ - [`02_pretty_tables.py`](examples/02_pretty_tables.py) — `LoggingTable` for tabular console output
179
+ - [`03_progress_tracking.py`](examples/03_progress_tracking.py) — `logger.track()` in parallel workers
180
+ - [`04_caching.py`](examples/04_caching.py) — `@lru_cache` with disable-hook
181
+ - [`05_path_helpers.py`](examples/05_path_helpers.py) — local + S3 path ops
182
+
183
+ ```bash
184
+ uv run examples/01_basic_logger.py
185
+ ```
186
+
187
+ ## Layout
188
+
189
+ ```
190
+ klogr/
191
+ ├── __init__.py # public re-exports
192
+ ├── logger.py # LoggingRich, LoggingTable, get_logger
193
+ ├── cache.py # lru_cache, DisableableLRUCache, sha256sum, get_cache_dir
194
+ ├── path/ # local + S3 path helpers
195
+ │ ├── __init__.py
196
+ │ ├── ops.py # pure ops (join, dirname, basename, suffix, …)
197
+ │ ├── env.py # path_dotenv, path_home, path_expanduser
198
+ │ ├── io.py # mkdir, remove, copy, move, read/write
199
+ │ └── query.py # exists, is_dir, glob, listdir, stat
200
+ └── time.py # get_elapsed_time
201
+ ```
202
+
203
+ ## Build & test
204
+
205
+ ```bash
206
+ uv sync
207
+ .venv/bin/pre-commit run --all-files
208
+ .venv/bin/pytest
209
+ .venv/bin/mypy klogr/
210
+ ```
211
+
212
+ ## License
213
+
214
+ MIT — see [LICENSE.md](LICENSE.md).