Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
857 commits
Select commit Hold shift + click to select a range
87dd6d2
remove mozjpeg from build.Dockerfile
ajslater Jun 26, 2025
9c696c8
Merge branch 'main' into develop
ajslater Jun 26, 2025
2d75c4c
mkdocs
ajslater Jul 18, 2025
b791de8
use dry run lockfile for updated
ajslater Jul 18, 2025
8ea942c
configure mkdocs properly
ajslater Jul 18, 2025
0205bfa
tilde versions for deps
ajslater Jul 18, 2025
13aa16a
add html section to readme
ajslater Jul 18, 2025
42db67c
update deps
ajslater Jul 25, 2025
3f60ea5
update deps
ajslater Jul 30, 2025
c9c6821
revise project urls
ajslater Jul 30, 2025
6b3785b
update deps add ty. typecheck with ty
ajslater Aug 6, 2025
2056625
uv_build
ajslater Aug 9, 2025
f8573fd
update scripts, pyproject, eslint, depts, fix lints
ajslater Aug 24, 2025
ce762d9
update deps
ajslater Aug 24, 2025
f2db131
update deps ane eslint config
ajslater Sep 13, 2025
757069f
update deps
ajslater Oct 9, 2025
74dcf7c
doesn't work with python 3.14
ajslater Oct 10, 2025
34a2990
fix builder dockerfile
ajslater Oct 10, 2025
49bb2de
copy debian sources on build properly for ci
ajslater Oct 10, 2025
6f70ef8
format
ajslater Oct 10, 2025
a79df7b
fix tests for new versions of external programs on darwin and somewha…
ajslater Oct 10, 2025
617bab3
fix formatting and use python 3.13 for build
ajslater Oct 10, 2025
0fb68a8
format
ajslater Oct 11, 2025
d821573
fix linux test file sizes
ajslater Oct 11, 2025
c336fa9
more test size changes for linux
ajslater Oct 11, 2025
8062c1f
remove test subdirs
ajslater Oct 11, 2025
0a2c35b
fix webp sizes in tests for linux
ajslater Oct 11, 2025
dbadc02
try to fix pre optimized webp on linux again
ajslater Oct 11, 2025
56ec580
fix sizes again
ajslater Oct 11, 2025
daa53df
one more size
ajslater Oct 11, 2025
aeadb55
Merge branch 'main' into develop
ajslater Oct 11, 2025
c0089a3
update deps
ajslater Oct 15, 2025
81bf34f
update deps
ajslater Oct 15, 2025
d00fa0f
add ty to makefile
ajslater Oct 15, 2025
640a7e1
delete deprecated pillow code
ajslater Oct 27, 2025
59f0670
support python 3.14, remove old pyright ignores
ajslater Oct 27, 2025
a0a5de1
bump version and news
ajslater Oct 27, 2025
fb66fbe
Merge branch 'main' into develop
ajslater Oct 27, 2025
1bfdde5
update deps
ajslater Nov 3, 2025
0d5fe8a
update deps
ajslater Nov 6, 2025
93515ed
update deps
ajslater Nov 8, 2025
104e02b
try to get duration info from detect format and PIL
ajslater Nov 8, 2025
b90d616
use webpmux for webp animated
ajslater Nov 8, 2025
9563570
update deps, update pytest config
ajslater Nov 9, 2025
cba2a31
change container test names
ajslater Nov 9, 2025
951fe2c
fix PIL jpeg conversion size for mpo test
ajslater Nov 9, 2025
1d67030
replace old test animated webp with one without dropped frames
ajslater Nov 9, 2025
449e7cd
enable im2webpanimatedlossless permanently enable webpumx, adjust fil…
ajslater Nov 9, 2025
35dd24c
fix adding info to correct handler clases constructor
ajslater Nov 9, 2025
a9e2371
add icecream importer
ajslater Nov 9, 2025
f1a9feb
bump news and version
ajslater Nov 9, 2025
4463b60
more news
ajslater Nov 9, 2025
d377684
simplify run_ext_fs for webp no longer needing output_path. compact a…
ajslater Nov 9, 2025
d5c891e
change webp arg order
ajslater Nov 9, 2025
0528786
use python secure tmpdir for webp temporary files
ajslater Nov 9, 2025
c9f4948
fix news version number
ajslater Nov 9, 2025
a028fad
fix test webp sizesfor linux
ajslater Nov 9, 2025
801fcd6
Merge branch 'main' into develop
ajslater Nov 9, 2025
71bcf40
update deps
ajslater Nov 23, 2025
d798acb
fix method and variable speling
ajslater Dec 1, 2025
8c22066
type checking fixes
ajslater Dec 1, 2025
0b13666
reduct complexity
ajslater Dec 1, 2025
d659d5d
complexipy & dep groups
ajslater Dec 19, 2025
ae0842d
reduce complexity of create handler even more
ajslater Dec 19, 2025
f484521
clean now cleans all caches
ajslater Dec 19, 2025
dbbc9be
update deps
ajslater Dec 19, 2025
2df0862
fix image format detection
ajslater Dec 20, 2025
296d77e
isolate handler external command tuples for debugging
ajslater Dec 20, 2025
d42dc18
fix extracting info from images
ajslater Dec 20, 2025
d4a314a
fix conversion to 7zr
ajslater Jan 13, 2026
eba8708
update deps
ajslater Jan 13, 2026
1cdec72
remove unused ty ignores
ajslater Jan 13, 2026
4dcd6b1
ty ignores
ajslater Jan 13, 2026
f063a91
update deps
ajslater Jan 21, 2026
0a248ee
update deps
ajslater Jan 26, 2026
98b1eb2
update deps
ajslater Jan 26, 2026
aa12963
update deps
ajslater Jan 29, 2026
1a56304
turn some const sets into tuples for type compatibility
ajslater Jan 30, 2026
a6b9589
use tuple instead of list
ajslater Jan 30, 2026
7361176
type get_config method. remove useless warning raise
ajslater Jan 30, 2026
ca8f27f
update deps
ajslater Jan 31, 2026
fa4194f
fix attrdict type
ajslater Jan 31, 2026
a423013
update deps
ajslater Feb 1, 2026
00e9fed
fix mpo test
ajslater Feb 1, 2026
ab0b41e
bump version and pin confuse, bump news
ajslater Feb 1, 2026
58cc71e
fix test script with test dependency group
ajslater Feb 1, 2026
76c2cb7
fix lint script with group
ajslater Feb 1, 2026
f7ec6bd
Merge branch 'main' into develop
ajslater Feb 1, 2026
f27c5aa
change confuse version specifier
ajslater Feb 1, 2026
0e309c9
use dockerfmt. update deps. ruff converts inline ifs to ors
ajslater Feb 10, 2026
178636b
update build dockerfile
ajslater Feb 10, 2026
6197292
update deps and add eslint plugins
ajslater Feb 10, 2026
b6e95cb
add mbake
ajslater Feb 10, 2026
494e171
remove unused ruff noqas
ajslater Feb 13, 2026
cf2ec87
Add types with pyre-check
ajslater Feb 13, 2026
a8fd71b
infer types. bump version and news. add py.typed
ajslater Feb 14, 2026
6e21b1a
Squashed commit of the following:
ajslater Feb 15, 2026
52f507f
update devenv and deps
ajslater Feb 15, 2026
7446ce1
update devenv
ajslater Feb 16, 2026
af8f417
update devs and devenv
ajslater Feb 16, 2026
a22eadb
update devenv
ajslater Feb 16, 2026
3ec7ade
add option for ignore archive entry mtimes
ajslater Feb 16, 2026
2866691
change option name
ajslater Feb 17, 2026
b8c0518
update devenv add missing ci script
ajslater Feb 17, 2026
dd08d8f
fix script hashbang
ajslater Feb 17, 2026
cf62dcc
move docker-compose to compose remove ignored version
ajslater Feb 17, 2026
2a6983c
try running script with simpler sh
ajslater Feb 17, 2026
739d518
add debug command for circlecir persist
ajslater Feb 17, 2026
56a9c39
chown dist before store
ajslater Feb 17, 2026
24fa5e6
speling
ajslater Feb 17, 2026
d384d30
Merge branch 'main' into develop
ajslater Feb 17, 2026
16d9325
update devenv
ajslater Feb 25, 2026
3963900
remove cruft
ajslater Feb 25, 2026
00cc86a
update makefile
ajslater Feb 25, 2026
7f574a7
update deps
ajslater Feb 26, 2026
b2f0c27
update devenv
ajslater Mar 2, 2026
87ef503
update devenv
ajslater Mar 12, 2026
9132a50
update devenv
ajslater Mar 14, 2026
a3fd72c
update devenv
ajslater Mar 14, 2026
0d66974
update devevn
ajslater Mar 15, 2026
ca108f9
update devenv
ajslater Mar 22, 2026
ddbe5af
update devenv & deps
ajslater Mar 31, 2026
101a9c6
update deps
ajslater Apr 1, 2026
26ee70f
update devevn & deps
ajslater Apr 8, 2026
6b5e88b
Squashed commit of the following:
ajslater Apr 8, 2026
036638b
dockerfile becomes builder
ajslater Apr 8, 2026
ba48a21
update devenv
ajslater Apr 8, 2026
84757ef
Squashed commit of the following:
ajslater Apr 10, 2026
9b12ef5
add cancelled if to gha steps
ajslater Apr 10, 2026
9a18e57
fix gha file cancelled() function
ajslater Apr 10, 2026
dbf52cd
install tool for tests
ajslater Apr 10, 2026
9212076
update devenv
ajslater Apr 10, 2026
73bb1df
Merge branch 'main' into develop
ajslater Apr 10, 2026
3242a0c
remove circleci remnamnts
ajslater Apr 10, 2026
13d110b
format dockerfile
ajslater Apr 10, 2026
86ee7e4
add a set_do_repack() method for the upcoming scheduler to use
ajslater Apr 10, 2026
456d0d2
for safety have buffered objects write to the tempfile before replaci…
ajslater Apr 10, 2026
275ce8b
Scheduler (#103)
ajslater Apr 12, 2026
b534686
Merge branch 'main' into develop
ajslater Apr 12, 2026
ad78506
treestamps 3.0
ajslater Apr 13, 2026
31ee42f
bump version and news
ajslater Apr 13, 2026
ea1d70d
Merge branch 'main' into develop
ajslater Apr 13, 2026
1756b57
update devenv. convert to bun
ajslater Apr 16, 2026
96d1a1b
Righttyper typing & corrections (#106)
ajslater Apr 16, 2026
413bf20
fix bun install on docker and gha compose
ajslater Apr 19, 2026
1091b2e
remove package-lock.json
ajslater Apr 19, 2026
f6404e4
update devenv
ajslater Apr 19, 2026
3ebaeb2
only copy bun deps first for dockerfile
ajslater Apr 19, 2026
95cf1d8
update devenv
ajslater Apr 19, 2026
edcae13
update devenv
ajslater Apr 19, 2026
e3dd03d
Fix BufferedReader pickling error when timestamps file exists (#107)
ajslater Apr 20, 2026
d86fcc7
print exception traces
ajslater Apr 20, 2026
34c626a
update deps
ajslater Apr 20, 2026
a15203b
bump news and version
ajslater Apr 20, 2026
6bad1b5
install poppler utils in docker for pdf tests
ajslater Apr 20, 2026
c682b2a
update deps
ajslater Apr 20, 2026
79d5389
format dockerfile
ajslater Apr 20, 2026
93f101a
add bunx support
ajslater Apr 20, 2026
b8a0a8c
bump version and news to v6.2.0
ajslater Apr 20, 2026
923bc72
try with svgo in packages
ajslater Apr 21, 2026
4abe8d8
Merge branch 'main' into develop
ajslater Apr 21, 2026
4188151
Squashed commit of the following:
ajslater Apr 29, 2026
ffccaf2
fix pyright warnings
ajslater Apr 29, 2026
34b22a0
ty ignore
ajslater Apr 29, 2026
e3e725f
update treestamps
ajslater Apr 29, 2026
6b2f6fd
Merge branch 'main' into develop
ajslater Apr 29, 2026
cc0187e
fix MPO error for 6.3.1
ajslater Apr 29, 2026
51305ac
Migrate to confuse 2.2.0 by replacing AttrDict with PicoptSettings (#…
ajslater Apr 29, 2026
d3b1f36
Drop dead isinstance check; split Scheduler._submit_ready_job (#116)
ajslater Apr 29, 2026
79320eb
update deps
ajslater Apr 29, 2026
8dfdfaa
bump version and news to 6.4.0
ajslater Apr 29, 2026
bb4ebea
Merge branch 'main' into develop
ajslater Apr 29, 2026
afde2b6
update devenv
ajslater May 4, 2026
70f810d
Format argparse help with rich-argparse (#118)
ajslater May 5, 2026
0c95ec7
bump version and news to 6.5.0
ajslater May 5, 2026
72cdfe3
typecheck
ajslater May 5, 2026
c1fcddb
Defer animated WebP duration extraction to handler (#119)
ajslater May 5, 2026
4925375
bump news for animated deferall
ajslater May 5, 2026
c708dc0
Cache file header bytes on PathInfo for detectors (#120)
ajslater May 5, 2026
f77d701
bump news
ajslater May 5, 2026
4210bb2
Single Image.open in _extract_image_info; read metadata before verify…
ajslater May 5, 2026
cc7a519
bump news
ajslater May 5, 2026
6c94267
Merge branch 'main' into develop
ajslater May 5, 2026
7c0c320
Merge branch 'main' into develop
ajslater May 5, 2026
122834a
update devenv and fix typechecking
ajslater May 22, 2026
fa1cf05
remove use of prettier sh plugin'
ajslater May 22, 2026
01074bd
fix data loss when converting without external tools
ajslater May 27, 2026
7e415f0
doctor: show platform-specific install hints for missing tools
ajslater May 27, 2026
138efa9
decomplexify doctor
ajslater May 27, 2026
3c45b4b
update deps
ajslater May 27, 2026
5733bf1
bump version and news
ajslater May 27, 2026
840d71d
update devenv. remove old prettier sh plugin
ajslater May 27, 2026
322bde1
Merge branch 'main' into develop
ajslater May 27, 2026
b84c49b
replace pyoxipng with oxipng-pybind (#124)
ajslater May 28, 2026
f07d3b7
update deps
ajslater May 28, 2026
2600dcf
bump news and version to v6.5.2
ajslater May 28, 2026
828004f
Merge branch 'main' into develop
ajslater May 28, 2026
057bd9c
update deps
ajslater Jun 25, 2026
407a643
update deps
ajslater Jul 1, 2026
e22370c
Add memory-aware scheduling to prevent OOM on large archives
ajslater Jul 1, 2026
358aead
update deps and format
ajslater Jul 1, 2026
28ccb7a
Fix basedpyright and ty diagnostics
ajslater Jul 1, 2026
d3c1131
update devenv
ajslater Jul 1, 2026
3d8bf23
remove pytest-gitignore
ajslater Jul 2, 2026
05b880c
Fix critical data-loss bugs in file replacement and archive repacking
ajslater Jul 2, 2026
ed8ffb5
Reduce _select_pipeline_for_handler cognitive complexity
ajslater Jul 2, 2026
2128b45
Fix scheduler and walk error-path correctness
ajslater Jul 2, 2026
07f7029
Preserve archive metadata fidelity through repack
ajslater Jul 2, 2026
5d8be7a
Fix config layering, validation, and doctor accounting
ajslater Jul 2, 2026
9fda386
Fix webp lossless detection, png depth parsing, and handler edge cases
ajslater Jul 2, 2026
05c1f45
Make PDF and SVG optimization safer
ajslater Jul 2, 2026
2df08ac
Survive symlink loops and unreadable directories
ajslater Jul 2, 2026
65f6324
Revive in-archive entry skipping and timestamp consumption
ajslater Jul 3, 2026
482e802
Improve scheduler-thread and archive throughput
ajslater Jul 3, 2026
c86d469
Extract dir timestamping, wire staging cleanup, untangle reporter dis…
ajslater Jul 5, 2026
22cebb4
Split the webp plugin into a package
ajslater Jul 5, 2026
2a6e7ed
Deduplicate handler routing and document the PathInfo contract
ajslater Jul 5, 2026
d163ce6
Memoize tool probes
ajslater Jul 5, 2026
44ecc57
Add per-directory .picopt.yaml config files and config-writing flags
ajslater Jul 5, 2026
a2bcd82
Add fail-fast, cancellation, idempotency, and pixel-identity tests
ajslater Jul 5, 2026
136a435
Update NEWS for v6.6.0
ajslater Jul 5, 2026
b301776
Add missing v6.6.0 news items
ajslater Jul 5, 2026
2ee5a9e
version 6.6.0
ajslater Jul 5, 2026
e5b58b7
Merge branch 'main' into develop
ajslater Jul 5, 2026
965bb04
Fix repeated "Optimizing formats" banner with -W and planted .picopt.…
ajslater Jul 5, 2026
bd0ba5d
bump version to 6.6.1
ajslater Jul 5, 2026
c3eb8b0
bump news
ajslater Jul 5, 2026
d373db6
Fix animated WebP optimization failing on zero-duration frames
ajslater Jul 6, 2026
b707ca0
update deps
ajslater Jul 6, 2026
1771f7f
Clear basedpyright typecheck warnings
ajslater Jul 6, 2026
8458275
fix news formattign
ajslater Jul 6, 2026
b71aa1b
Reduce cyclomatic complexity of four rank-C functions
ajslater Jul 6, 2026
cdfd461
Merge branch 'main' into develop
ajslater Jul 6, 2026
489cec1
Print verbose config summaries only once, not per directory config
ajslater Jul 9, 2026
727dc49
update deps and bump version to 6.6.2
ajslater Jul 9, 2026
47f515b
Add v6.6.2 news entry for repeated verbose summaries fix
ajslater Jul 9, 2026
317545b
Fix timestamps silently invalidated by tree-root .picopt.yaml configs
ajslater Jul 9, 2026
09474b3
update deps
ajslater Jul 9, 2026
eca6721
Merge branch 'main' into develop
ajslater Jul 9, 2026
6406fa2
Report corrupt images as clean warnings instead of dumping tracebacks
ajslater Jul 10, 2026
445ab31
version 6.6.3
ajslater Jul 10, 2026
d07da5a
update deps
ajslater Jul 14, 2026
a8ecb01
Fix ty and remark-lint warnings
ajslater Jul 25, 2026
2af7663
Jxl format (#129)
ajslater Jul 29, 2026
455548a
update deps
ajslater Jul 29, 2026
e2bf8a9
Merge branch 'main' into develop
ajslater Jul 29, 2026
62fde6a
v6.8.0: timestamps survive upgrades that record new options
ajslater Aug 2, 2026
f37b54a
update deps
ajslater Aug 3, 2026
c960db7
Write config files atomically
ajslater Aug 3, 2026
0a5f45d
Note the atomic config writes in the v6.8.0 news
ajslater Aug 3, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,31 @@
# 📰 Picopt News

## v6.8.0

### Fixes

- Config files are written atomically. A crash or full disk during `-w`, `-W`,
or `--write-config-file` can no longer truncate a hand-written config.

### Features

- Timestamps survive picopt upgrades that record new options. Only a changed
option value invalidates them, so stamp files written before an option joined
the recorded set stay valid at its default. Timestamps from before 6.7.0 work
again.
- Timestamp files now open with a comment explaining what they are, that they
are machine-written, and that deleting one just re-optimizes that tree.
- Discarding timestamps because a sub-directory `.picopt.yaml` changed now says
so, instead of naming an internal key.
- README documents `.picopt_treestamps.yaml`: what it records, that it is never
applied as configuration, and that it is safe to delete.

### Changes

- Requires treestamps 5.0.0. Timestamp files no longer duplicate `ignore` and
`symlinks` in a second block. Existing timestamp files still load, and
upgrading invalidates nothing.

## v6.7.0

### Features
Expand Down
38 changes: 36 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -312,8 +312,9 @@ Any config key is accepted and validated, but run-scoped keys — `dry_run`,
`list_only`, `timestamps`, `after`, `jobs`, `memory_limit`, `fail_fast`,
`fail_fast_container`, `verbose`, and `paths` — are governed by the run-level
value; setting them in a directory file has no per-directory effect. When
timestamps (`-t`) are enabled, editing, adding, or removing any `.picopt.yaml`
re-processes its tree on the next run.
timestamps (`-t`) are enabled, editing an option value in any `.picopt.yaml`, or
adding or removing one, re-processes its tree on the next run. Comment and
formatting edits do not.

### Writing config files

Expand All @@ -326,6 +327,39 @@ re-processes its tree on the next run.
Existing keys and comments in the target file survive the merge. Run-mode flags
(`-d`, `-L`, verbosity) and the target paths are never persisted.

### The two files

Picopt keeps two kinds of file in your directories, and they are opposites:

`.picopt.yaml` is **your input**. You write it, picopt only reads it (or merges
your invoked options into it when you ask with `-W`). Commit it.

`.picopt_treestamps.yaml` — plus a `.picopt_treestamps.wal.yaml` while a run is
in progress — is **picopt's memory**. It records when each file was last
optimized, so already-optimized files are skipped on the next `-t` run. It is
written by the program, never applied as configuration, and safe to delete: the
tree is simply re-optimized on the next run. Keep it out of version control or
commit it, whichever suits you; picopt does not care.

Its header holds a snapshot of the options that produced those timestamps — not
a copy of your `.picopt.yaml`, but of the _resolved_ options from every layer,
including flags and environment variables that live in no file. When those
options change, the recorded timestamps no longer describe what a run would do,
so they are discarded and the tree is re-examined. Picopt prints which options
differed. Options that don't affect which files are selected are not recorded,
and adding or retiring a recorded option does not invalidate anything by itself:
only a changed value does.

Timestamp files you should know about:

- Deleting one re-optimizes that tree.
- Running from a higher directory absorbs and removes the stamp files beneath
it.
- Stamp files inside archives are consumed and dropped when the archive is
repacked.
- `-N` / `--timestamps-no-check-config` skips the option comparison for a run,
keeping timestamps that would otherwise be discarded.

## ⌨️ Use Examples

Optimize all JPEG files in a directory:
Expand Down
16 changes: 2 additions & 14 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@
"@prettier/plugin-xml": "^3.4.2",
"@stylistic/eslint-plugin": "^5.10.0",
"eslint": "^10.8.0",
"eslint_d": "^15.0.2",
"eslint_d": "^15.0.3",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-array-func": "^5.1.1",
"eslint-plugin-compat": "^7.0.2",
Expand All @@ -87,7 +87,7 @@
"eslint-plugin-sonarjs": "^4.2.0",
"eslint-plugin-toml": "^1.5.0",
"eslint-plugin-unicorn": "^72.0.0",
"eslint-plugin-yml": "^3.6.0",
"eslint-plugin-yml": "^3.7.0",
"prettier": "^3.9.6",
"prettier-plugin-nginx": "^1.0.3",
"prettier-plugin-packagejson": "^3.0.2",
Expand Down
31 changes: 24 additions & 7 deletions picopt/config/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import time
from contextlib import suppress
from pathlib import Path
from tempfile import NamedTemporaryFile
from typing import TYPE_CHECKING, Any, Final

from confuse import Configuration, MappingTemplate
Expand Down Expand Up @@ -208,8 +209,10 @@ def merge_config_file(target: Path, base_path: Path, options: dict) -> None:
Merge ``options`` into ``base_path``'s config section and write to ``target``.

Round-trip loads ``base_path`` so existing keys and comments survive,
then writes owner-only. Raises ``YAMLError`` or ``OSError`` on
read/write failure so callers decide whether it is fatal.
then writes owner-only via a sibling temp file and an atomic replace, so
a crash mid-write cannot truncate a hand-authored config. Raises
``YAMLError`` or ``OSError`` on read/write failure so callers decide
whether it is fatal.
"""
yaml = YAML()
data = yaml.load(base_path.read_text()) if base_path.is_file() else None
Expand All @@ -221,11 +224,25 @@ def merge_config_file(target: Path, base_path: Path, options: dict) -> None:
data[PROGRAM_NAME] = section
section.update(options)
target.parent.mkdir(parents=True, exist_ok=True)
with target.open("w") as stream:
yaml.dump(data, stream)
# Best effort: filesystems without POSIX modes (e.g. FAT) just skip it.
with suppress(OSError):
target.chmod(0o600)
# A unique temp name, not a fixed sibling: two concurrent writers to the
# same target would otherwise unlink each other's temp mid-write. Same
# directory keeps the replace off a cross-device path.
tmp = NamedTemporaryFile( # noqa: SIM115
"w", dir=target.parent, prefix=target.name + ".", delete=False
)
tmp_path = Path(tmp.name)
try:
with tmp:
yaml.dump(data, tmp)
tmp.flush()
os.fsync(tmp.fileno())
# Best effort: filesystems without POSIX modes (e.g. FAT) just skip
# it. Set before the replace so the target is never briefly wider.
with suppress(OSError):
tmp_path.chmod(0o600)
tmp_path.replace(target)
finally:
tmp_path.unlink(missing_ok=True)


def _write_merged_config(
Expand Down
31 changes: 30 additions & 1 deletion picopt/config/consts.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,14 @@
allowlist, which doesn't depend on the format registry at all.
"""

from typing import Final
from collections.abc import Mapping
from importlib.resources import files
from types import MappingProxyType
from typing import Any, Final

from ruamel.yaml import YAML

from picopt import PROGRAM_NAME

# Per-directory config files layer beneath env vars and CLI args but above
# the user config. Named to be distinct from the timestamps file
Expand All @@ -35,3 +42,25 @@
"timestamps_ignore_archive_entry_mtimes",
}
)


def _packaged_defaults() -> Mapping[str, Any]:
"""Load the packaged default config values."""
text = (files(PROGRAM_NAME) / "config_default.yaml").read_text()
return YAML(typ="safe").load(text)[PROGRAM_NAME]


# Treestamps fills keys missing from either side of a config comparison
# from these before diffing, so adding or retiring a recorded key does not
# invalidate stamp files written before the change. A KeyError here is a
# deliberate import-time guard: every recorded key needs a packaged default.
TIMESTAMPS_CONFIG_DEFAULTS: Final[Mapping[str, Any]] = MappingProxyType(
{key: _packaged_defaults()[key] for key in TIMESTAMPS_CONFIG_KEYS}
)

# When a key leaves TIMESTAMPS_CONFIG_KEYS, move its last packaged default
# here: stamp files that recorded it at its default stay valid, while ones
# that recorded another value still invalidate. Keys *added* to
# TIMESTAMPS_CONFIG_KEYS must default to behavior-preserving values,
# because a key missing from an old stamp file reads as the current default.
RETIRED_TIMESTAMPS_CONFIG_DEFAULTS: Final[Mapping[str, Any]] = MappingProxyType({})
Loading